//// AnimNodeLoader.cpp//// Created by Anthony J. Thibault on 9/2/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 "AnimNodeLoader.h" #include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QFile> #include "AnimNode.h"#include "AnimClip.h"#include "AnimBlendLinear.h"#include "AnimBlendLinearMove.h"#include "AnimationLogging.h"#include "AnimOverlay.h"#include "AnimStateMachine.h"#include "AnimRandomSwitch.h"#include "AnimManipulator.h"#include "AnimInverseKinematics.h"#include "AnimDefaultPose.h"#include "AnimTwoBoneIK.h"#include "AnimSplineIK.h"#include "AnimPoleVectorConstraint.h"#include "AnimBlendDirectional.h"#include "AnimUtil.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); // factory functionsstatic 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 loadBlendLinearMoveNode(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 loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);static AnimNode::Pointer loadBlendDirectionalNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; // called after children have been loaded// returns node on success, nullptr on failure.static bool processDoNothing(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);bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);bool processBlendDirectionalNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return "clip"; case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::BlendLinearMove: return "blendLinearMove"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::RandomSwitchStateMachine: return "randomSwitchStateMachine"; case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::BlendDirectional: return "blendDirectional"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr;} static AnimNode::Type stringToAnimNodeType(const QString& str) { // O(n), move to map when number of types becomes large. const int NUM_TYPES = static_cast<int>(AnimNode::Type::NumTypes); for (int i = 0; i < NUM_TYPES; i++) { AnimNode::Type type = static_cast<AnimNode::Type>(i); if (str == animNodeTypeToString(type)) { return type; } } 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 if (str == "evaluateBoth") { return AnimStateMachine::InterpType::EvaluateBoth; } else { return AnimStateMachine::InterpType::NumTypes; }} static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) { if (str == "snapshotBoth") { return AnimRandomSwitch::InterpType::SnapshotBoth; } else if (str == "snapshotPrev") { return AnimRandomSwitch::InterpType::SnapshotPrev; } else if (str == "evaluateBoth") { return AnimRandomSwitch::InterpType::EvaluateBoth; } else { return AnimRandomSwitch::InterpType::NumTypes; }} static EasingType stringToEasingType(const QString& str) { if (str == "linear") { return EasingType_Linear; } else if (str == "easeInSine") { return EasingType_EaseInSine; } else if (str == "easeOutSine") { return EasingType_EaseOutSine; } else if (str == "easeInOutSine") { return EasingType_EaseInOutSine; } else if (str == "easeInQuad") { return EasingType_EaseInQuad; } else if (str == "easeOutQuad") { return EasingType_EaseOutQuad; } else if (str == "easeInOutQuad") { return EasingType_EaseInOutQuad; } else if (str == "easeInCubic") { return EasingType_EaseInCubic; } else if (str == "easeOutCubic") { return EasingType_EaseOutCubic; } else if (str == "easeInOutCubic") { return EasingType_EaseInOutCubic; } else if (str == "easeInQuart") { return EasingType_EaseInQuart; } else if (str == "easeOutQuart") { return EasingType_EaseOutQuart; } else if (str == "easeInOutQuart") { return EasingType_EaseInOutQuart; } else if (str == "easeInQuint") { return EasingType_EaseInQuint; } else if (str == "easeOutQuint") { return EasingType_EaseOutQuint; } else if (str == "easeInOutQuint") { return EasingType_EaseInOutQuint; } else if (str == "easeInExpo") { return EasingType_EaseInExpo; } else if (str == "easeOutExpo") { return EasingType_EaseOutExpo; } else if (str == "easeInOutExpo") { return EasingType_EaseInOutExpo; } else if (str == "easeInCirc") { return EasingType_EaseInCirc; } else if (str == "easeOutCirc") { return EasingType_EaseOutCirc; } else if (str == "easeInOutCirc") { return EasingType_EaseInOutCirc; } else { return EasingType_NumTypes; }} static AnimBlendType stringToAnimBlendType(const QString& str) { if (str == "normal") { return AnimBlendType_Normal; } else if (str == "addRelative") { return AnimBlendType_AddRelative; } else if (str == "addAbsolute") { return AnimBlendType_AddAbsolute; } else { return AnimBlendType_NumTypes; }} static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::Absolute: return "absolute"; case AnimManipulator::JointVar::Type::Relative: return "relative"; case AnimManipulator::JointVar::Type::UnderPose: return "underPose"; case AnimManipulator::JointVar::Type::Default: return "default"; case AnimManipulator::JointVar::Type::NumTypes: return nullptr; }; return nullptr;} static AnimManipulator::JointVar::Type stringToAnimManipulatorJointVarType(const QString& str) { // O(n), move to map when number of types becomes large. const int NUM_TYPES = static_cast<int>(AnimManipulator::JointVar::Type::NumTypes); for (int i = 0; i < NUM_TYPES; i++) { AnimManipulator::JointVar::Type type = static_cast<AnimManipulator::JointVar::Type>(i); if (str == animManipulatorJointVarTypeToString(type)) { return type; } } return AnimManipulator::JointVar::Type::NumTypes;} static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return loadClipNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::RandomSwitchStateMachine: return loadRandomSwitchStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::BlendDirectional: return loadBlendDirectionalNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr;} static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return processDoNothing; case AnimNode::Type::BlendLinear: return processDoNothing; case AnimNode::Type::BlendLinearMove: return processDoNothing; case AnimNode::Type::Overlay: return processDoNothing; case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::RandomSwitchStateMachine: return processRandomSwitchStateMachineNode; case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::BlendDirectional: return processBlendDirectionalNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr;} #define READ_STRING(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isString()) { \ qCCritical(animation) << "AnimNodeLoader, error reading string" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ QString NAME = NAME##_VAL.toString() #define READ_OPTIONAL_STRING(NAME, JSON_OBJ) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ QString NAME; \ if (NAME##_VAL.isString()) { \ NAME = NAME##_VAL.toString(); \ } #define READ_BOOL(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isBool()) { \ qCCritical(animation) << "AnimNodeLoader, error reading bool" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ bool NAME = NAME##_VAL.toBool() #define READ_OPTIONAL_BOOL(NAME, JSON_OBJ, DEFAULT) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ bool NAME = DEFAULT; \ if (NAME##_VAL.isBool()) { \ NAME = NAME##_VAL.toBool(); \ } \ do {} while (0) #define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isDouble()) { \ qCCritical(animation) << "AnimNodeLoader, error reading double" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ float NAME = (float)NAME##_VAL.toDouble() #define READ_OPTIONAL_FLOAT(NAME, JSON_OBJ, DEFAULT) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ float NAME = (float)DEFAULT; \ if (NAME##_VAL.isDouble()) { \ NAME = (float)NAME##_VAL.toDouble(); \ } \ do {} while (0) #define READ_VEC3(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isArray()) { \ qCCritical(animation) << "AnimNodeLoader, error reading vector" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ QJsonArray NAME##_ARRAY = NAME##_VAL.toArray(); \ if (NAME##_ARRAY.size() != 3) { \ qCCritical(animation) << "AnimNodeLoader, vector size != 3" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ glm::vec3 NAME((float)NAME##_ARRAY.at(0).toDouble(), \ (float)NAME##_ARRAY.at(1).toDouble(), \ (float)NAME##_ARRAY.at(2).toDouble()) static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { auto idVal = jsonObj.value("id"); if (!idVal.isString()) { qCCritical(animation) << "AnimNodeLoader, bad string \"id\""; return nullptr; } QString id = idVal.toString(); auto typeVal = jsonObj.value("type"); if (!typeVal.isString()) { qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id; return nullptr; } QString typeStr = typeVal.toString(); AnimNode::Type type = stringToAnimNodeType(typeStr); if (type == AnimNode::Type::NumTypes) { qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id; return nullptr; } auto dataValue = jsonObj.value("data"); if (!dataValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id; return nullptr; } auto dataObj = dataValue.toObject(); std::vector<QString> outputJoints; auto outputJoints_VAL = dataObj.value("outputJoints"); if (outputJoints_VAL.isArray()) { QJsonArray outputJoints_ARRAY = outputJoints_VAL.toArray(); for (int i = 0; i < outputJoints_ARRAY.size(); i++) { outputJoints.push_back(outputJoints_ARRAY.at(i).toString()); } } assert((int)type >= 0 && type < AnimNode::Type::NumTypes); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); if (!node) { return nullptr; } auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id; return nullptr; } auto childrenArray = childrenValue.toArray(); for (const auto& childValue : childrenArray) { if (!childValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id; return nullptr; } AnimNode::Pointer child = loadNode(childValue.toObject(), jsonUrl); if (child) { node->addChild(child); } else { return nullptr; } } if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { for (auto&& outputJoint : outputJoints) { node->addOutputJoint(outputJoint); } return node; } else { return nullptr; }} static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_STRING(url, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(startFrame, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(endFrame, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false); READ_OPTIONAL_STRING(blendType, jsonObj); READ_OPTIONAL_STRING(baseURL, jsonObj); READ_OPTIONAL_FLOAT(baseFrame, jsonObj, 0.0f); READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj); READ_OPTIONAL_STRING(timeScaleVar, jsonObj); READ_OPTIONAL_STRING(loopFlagVar, jsonObj); READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj); // animation urls can be relative to the containing url document. auto tempUrl = QUrl(url); tempUrl = jsonUrl.resolved(tempUrl); AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value if (!blendType.isEmpty()) { blendTypeEnum = stringToAnimBlendType(blendType); if (blendTypeEnum == AnimBlendType_NumTypes) { qCCritical(animation) << "AnimNodeLoader, bad blendType on clip, id = " << id; return nullptr; } } auto node = std::make_shared<AnimClip>(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag, blendTypeEnum, baseURL, baseFrame); if (!startFrameVar.isEmpty()) { node->setStartFrameVar(startFrameVar); } if (!endFrameVar.isEmpty()) { node->setEndFrameVar(endFrameVar); } if (!timeScaleVar.isEmpty()) { node->setTimeScaleVar(timeScaleVar); } if (!loopFlagVar.isEmpty()) { node->setLoopFlagVar(loopFlagVar); } if (!mirrorFlagVar.isEmpty()) { node->setMirrorFlagVar(mirrorFlagVar); } return node;} static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(blendType, jsonObj); READ_OPTIONAL_STRING(alphaVar, jsonObj); AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value if (!blendType.isEmpty()) { blendTypeEnum = stringToAnimBlendType(blendType); if (blendTypeEnum == AnimBlendType_NumTypes) { qCCritical(animation) << "AnimNodeLoader, bad blendType on blendLinear, id = " << id; return nullptr; } } auto node = std::make_shared<AnimBlendLinear>(id, alpha, blendTypeEnum); if (!alphaVar.isEmpty()) { node->setAlphaVar(alphaVar); } return node;} static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(desiredSpeed, jsonObj, id, jsonUrl, nullptr); std::vector<float> characteristicSpeeds; auto speedsValue = jsonObj.value("characteristicSpeeds"); if (!speedsValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"characteristicSpeeds\" in blendLinearMove node, id =" << id; return nullptr; } auto speedsArray = speedsValue.toArray(); for (const auto& speedValue : speedsArray) { if (!speedValue.isDouble()) { qCCritical(animation) << "AnimNodeLoader, bad number in \"characteristicSpeeds\", id =" << id; return nullptr; } float speedVal = (float)speedValue.toDouble(); characteristicSpeeds.push_back(speedVal); }; READ_OPTIONAL_STRING(alphaVar, jsonObj); READ_OPTIONAL_STRING(desiredSpeedVar, jsonObj); auto node = std::make_shared<AnimBlendLinearMove>(id, alpha, desiredSpeed, characteristicSpeeds); if (!alphaVar.isEmpty()) { node->setAlphaVar(alphaVar); } if (!desiredSpeedVar.isEmpty()) { node->setDesiredSpeedVar(desiredSpeedVar); } return node;Lines 1–500 of 1141. Load more
// // AnimNodeLoader.cpp // // Created by Anthony J. Thibault on 9/2/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 "AnimNodeLoader.h" #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QFile> #include "AnimNode.h" #include "AnimClip.h" #include "AnimBlendLinear.h" #include "AnimBlendLinearMove.h" #include "AnimationLogging.h" #include "AnimOverlay.h" #include "AnimStateMachine.h" #include "AnimRandomSwitch.h" #include "AnimManipulator.h" #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" #include "AnimTwoBoneIK.h" #include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" #include "AnimBlendDirectional.h" #include "AnimUtil.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); // 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 loadBlendLinearMoveNode(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 loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadBlendDirectionalNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; // called after children have been loaded // returns node on success, nullptr on failure. static bool processDoNothing(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); bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); bool processBlendDirectionalNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return "clip"; case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::BlendLinearMove: return "blendLinearMove"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::RandomSwitchStateMachine: return "randomSwitchStateMachine"; case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::BlendDirectional: return "blendDirectional"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; } static AnimNode::Type stringToAnimNodeType(const QString& str) { // O(n), move to map when number of types becomes large. const int NUM_TYPES = static_cast<int>(AnimNode::Type::NumTypes); for (int i = 0; i < NUM_TYPES; i++) { AnimNode::Type type = static_cast<AnimNode::Type>(i); if (str == animNodeTypeToString(type)) { return type; } } 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 if (str == "evaluateBoth") { return AnimStateMachine::InterpType::EvaluateBoth; } else { return AnimStateMachine::InterpType::NumTypes; } } static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) { if (str == "snapshotBoth") { return AnimRandomSwitch::InterpType::SnapshotBoth; } else if (str == "snapshotPrev") { return AnimRandomSwitch::InterpType::SnapshotPrev; } else if (str == "evaluateBoth") { return AnimRandomSwitch::InterpType::EvaluateBoth; } else { return AnimRandomSwitch::InterpType::NumTypes; } } static EasingType stringToEasingType(const QString& str) { if (str == "linear") { return EasingType_Linear; } else if (str == "easeInSine") { return EasingType_EaseInSine; } else if (str == "easeOutSine") { return EasingType_EaseOutSine; } else if (str == "easeInOutSine") { return EasingType_EaseInOutSine; } else if (str == "easeInQuad") { return EasingType_EaseInQuad; } else if (str == "easeOutQuad") { return EasingType_EaseOutQuad; } else if (str == "easeInOutQuad") { return EasingType_EaseInOutQuad; } else if (str == "easeInCubic") { return EasingType_EaseInCubic; } else if (str == "easeOutCubic") { return EasingType_EaseOutCubic; } else if (str == "easeInOutCubic") { return EasingType_EaseInOutCubic; } else if (str == "easeInQuart") { return EasingType_EaseInQuart; } else if (str == "easeOutQuart") { return EasingType_EaseOutQuart; } else if (str == "easeInOutQuart") { return EasingType_EaseInOutQuart; } else if (str == "easeInQuint") { return EasingType_EaseInQuint; } else if (str == "easeOutQuint") { return EasingType_EaseOutQuint; } else if (str == "easeInOutQuint") { return EasingType_EaseInOutQuint; } else if (str == "easeInExpo") { return EasingType_EaseInExpo; } else if (str == "easeOutExpo") { return EasingType_EaseOutExpo; } else if (str == "easeInOutExpo") { return EasingType_EaseInOutExpo; } else if (str == "easeInCirc") { return EasingType_EaseInCirc; } else if (str == "easeOutCirc") { return EasingType_EaseOutCirc; } else if (str == "easeInOutCirc") { return EasingType_EaseInOutCirc; } else { return EasingType_NumTypes; } } static AnimBlendType stringToAnimBlendType(const QString& str) { if (str == "normal") { return AnimBlendType_Normal; } else if (str == "addRelative") { return AnimBlendType_AddRelative; } else if (str == "addAbsolute") { return AnimBlendType_AddAbsolute; } else { return AnimBlendType_NumTypes; } } static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::Absolute: return "absolute"; case AnimManipulator::JointVar::Type::Relative: return "relative"; case AnimManipulator::JointVar::Type::UnderPose: return "underPose"; case AnimManipulator::JointVar::Type::Default: return "default"; case AnimManipulator::JointVar::Type::NumTypes: return nullptr; }; return nullptr; } static AnimManipulator::JointVar::Type stringToAnimManipulatorJointVarType(const QString& str) { // O(n), move to map when number of types becomes large. const int NUM_TYPES = static_cast<int>(AnimManipulator::JointVar::Type::NumTypes); for (int i = 0; i < NUM_TYPES; i++) { AnimManipulator::JointVar::Type type = static_cast<AnimManipulator::JointVar::Type>(i); if (str == animManipulatorJointVarTypeToString(type)) { return type; } } return AnimManipulator::JointVar::Type::NumTypes; } static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return loadClipNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::RandomSwitchStateMachine: return loadRandomSwitchStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::BlendDirectional: return loadBlendDirectionalNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; } static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return processDoNothing; case AnimNode::Type::BlendLinear: return processDoNothing; case AnimNode::Type::BlendLinearMove: return processDoNothing; case AnimNode::Type::Overlay: return processDoNothing; case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::RandomSwitchStateMachine: return processRandomSwitchStateMachineNode; case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::BlendDirectional: return processBlendDirectionalNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; } #define READ_STRING(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isString()) { \ qCCritical(animation) << "AnimNodeLoader, error reading string" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ QString NAME = NAME##_VAL.toString() #define READ_OPTIONAL_STRING(NAME, JSON_OBJ) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ QString NAME; \ if (NAME##_VAL.isString()) { \ NAME = NAME##_VAL.toString(); \ } #define READ_BOOL(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isBool()) { \ qCCritical(animation) << "AnimNodeLoader, error reading bool" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ bool NAME = NAME##_VAL.toBool() #define READ_OPTIONAL_BOOL(NAME, JSON_OBJ, DEFAULT) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ bool NAME = DEFAULT; \ if (NAME##_VAL.isBool()) { \ NAME = NAME##_VAL.toBool(); \ } \ do {} while (0) #define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isDouble()) { \ qCCritical(animation) << "AnimNodeLoader, error reading double" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ float NAME = (float)NAME##_VAL.toDouble() #define READ_OPTIONAL_FLOAT(NAME, JSON_OBJ, DEFAULT) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ float NAME = (float)DEFAULT; \ if (NAME##_VAL.isDouble()) { \ NAME = (float)NAME##_VAL.toDouble(); \ } \ do {} while (0) #define READ_VEC3(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isArray()) { \ qCCritical(animation) << "AnimNodeLoader, error reading vector" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ QJsonArray NAME##_ARRAY = NAME##_VAL.toArray(); \ if (NAME##_ARRAY.size() != 3) { \ qCCritical(animation) << "AnimNodeLoader, vector size != 3" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ return ERROR_RETURN; \ } \ glm::vec3 NAME((float)NAME##_ARRAY.at(0).toDouble(), \ (float)NAME##_ARRAY.at(1).toDouble(), \ (float)NAME##_ARRAY.at(2).toDouble()) static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { auto idVal = jsonObj.value("id"); if (!idVal.isString()) { qCCritical(animation) << "AnimNodeLoader, bad string \"id\""; return nullptr; } QString id = idVal.toString(); auto typeVal = jsonObj.value("type"); if (!typeVal.isString()) { qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id; return nullptr; } QString typeStr = typeVal.toString(); AnimNode::Type type = stringToAnimNodeType(typeStr); if (type == AnimNode::Type::NumTypes) { qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id; return nullptr; } auto dataValue = jsonObj.value("data"); if (!dataValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id; return nullptr; } auto dataObj = dataValue.toObject(); std::vector<QString> outputJoints; auto outputJoints_VAL = dataObj.value("outputJoints"); if (outputJoints_VAL.isArray()) { QJsonArray outputJoints_ARRAY = outputJoints_VAL.toArray(); for (int i = 0; i < outputJoints_ARRAY.size(); i++) { outputJoints.push_back(outputJoints_ARRAY.at(i).toString()); } } assert((int)type >= 0 && type < AnimNode::Type::NumTypes); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); if (!node) { return nullptr; } auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id; return nullptr; } auto childrenArray = childrenValue.toArray(); for (const auto& childValue : childrenArray) { if (!childValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id; return nullptr; } AnimNode::Pointer child = loadNode(childValue.toObject(), jsonUrl); if (child) { node->addChild(child); } else { return nullptr; } } if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { for (auto&& outputJoint : outputJoints) { node->addOutputJoint(outputJoint); } return node; } else { return nullptr; } } static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_STRING(url, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(startFrame, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(endFrame, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false); READ_OPTIONAL_STRING(blendType, jsonObj); READ_OPTIONAL_STRING(baseURL, jsonObj); READ_OPTIONAL_FLOAT(baseFrame, jsonObj, 0.0f); READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj); READ_OPTIONAL_STRING(timeScaleVar, jsonObj); READ_OPTIONAL_STRING(loopFlagVar, jsonObj); READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj); // animation urls can be relative to the containing url document. auto tempUrl = QUrl(url); tempUrl = jsonUrl.resolved(tempUrl); AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value if (!blendType.isEmpty()) { blendTypeEnum = stringToAnimBlendType(blendType); if (blendTypeEnum == AnimBlendType_NumTypes) { qCCritical(animation) << "AnimNodeLoader, bad blendType on clip, id = " << id; return nullptr; } } auto node = std::make_shared<AnimClip>(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag, blendTypeEnum, baseURL, baseFrame); if (!startFrameVar.isEmpty()) { node->setStartFrameVar(startFrameVar); } if (!endFrameVar.isEmpty()) { node->setEndFrameVar(endFrameVar); } if (!timeScaleVar.isEmpty()) { node->setTimeScaleVar(timeScaleVar); } if (!loopFlagVar.isEmpty()) { node->setLoopFlagVar(loopFlagVar); } if (!mirrorFlagVar.isEmpty()) { node->setMirrorFlagVar(mirrorFlagVar); } return node; } static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(blendType, jsonObj); READ_OPTIONAL_STRING(alphaVar, jsonObj); AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value if (!blendType.isEmpty()) { blendTypeEnum = stringToAnimBlendType(blendType); if (blendTypeEnum == AnimBlendType_NumTypes) { qCCritical(animation) << "AnimNodeLoader, bad blendType on blendLinear, id = " << id; return nullptr; } } auto node = std::make_shared<AnimBlendLinear>(id, alpha, blendTypeEnum); if (!alphaVar.isEmpty()) { node->setAlphaVar(alphaVar); } return node; } static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(desiredSpeed, jsonObj, id, jsonUrl, nullptr); std::vector<float> characteristicSpeeds; auto speedsValue = jsonObj.value("characteristicSpeeds"); if (!speedsValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"characteristicSpeeds\" in blendLinearMove node, id =" << id; return nullptr; } auto speedsArray = speedsValue.toArray(); for (const auto& speedValue : speedsArray) { if (!speedValue.isDouble()) { qCCritical(animation) << "AnimNodeLoader, bad number in \"characteristicSpeeds\", id =" << id; return nullptr; } float speedVal = (float)speedValue.toDouble(); characteristicSpeeds.push_back(speedVal); }; READ_OPTIONAL_STRING(alphaVar, jsonObj); READ_OPTIONAL_STRING(desiredSpeedVar, jsonObj); auto node = std::make_shared<AnimBlendLinearMove>(id, alpha, desiredSpeed, characteristicSpeeds); if (!alphaVar.isEmpty()) { node->setAlphaVar(alphaVar); } if (!desiredSpeedVar.isEmpty()) { node->setDesiredSpeedVar(desiredSpeedVar); } return node;