diff --git a/interface/resources/avatar/animations/idleWS.fbx b/interface/resources/avatar/animations/idleWS.fbx new file mode 100644 index 0000000000..e730165012 Binary files /dev/null and b/interface/resources/avatar/animations/idleWS.fbx differ diff --git a/interface/resources/avatar/animations/idleWS_all.fbx b/interface/resources/avatar/animations/idleWS_all.fbx new file mode 100644 index 0000000000..f9ac3dacfb Binary files /dev/null and b/interface/resources/avatar/animations/idleWS_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_LFF_all.fbx b/interface/resources/avatar/animations/idle_LFF_all.fbx new file mode 100644 index 0000000000..6904773cd5 Binary files /dev/null and b/interface/resources/avatar/animations/idle_LFF_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_RFF_all.fbx b/interface/resources/avatar/animations/idle_RFF_all.fbx new file mode 100644 index 0000000000..77ea06dc70 Binary files /dev/null and b/interface/resources/avatar/animations/idle_RFF_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_lookaround01.fbx b/interface/resources/avatar/animations/idle_lookaround01.fbx new file mode 100644 index 0000000000..fbea065713 Binary files /dev/null and b/interface/resources/avatar/animations/idle_lookaround01.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_armstretch.fbx b/interface/resources/avatar/animations/idle_once_armstretch.fbx new file mode 100644 index 0000000000..23eeed3b26 Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_armstretch.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_bigstretch.fbx b/interface/resources/avatar/animations/idle_once_bigstretch.fbx new file mode 100644 index 0000000000..5e4731279f Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_bigstretch.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_checkwatch.fbx b/interface/resources/avatar/animations/idle_once_checkwatch.fbx new file mode 100644 index 0000000000..888d0bcbfc Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_checkwatch.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_headtilt.fbx b/interface/resources/avatar/animations/idle_once_headtilt.fbx new file mode 100644 index 0000000000..21d1bc43c8 Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_headtilt.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_lookaround.fbx b/interface/resources/avatar/animations/idle_once_lookaround.fbx new file mode 100644 index 0000000000..15be33092c Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_lookaround.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_neckstretch.fbx b/interface/resources/avatar/animations/idle_once_neckstretch.fbx new file mode 100644 index 0000000000..3968b96615 Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_neckstretch.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_slownod.fbx b/interface/resources/avatar/animations/idle_once_slownod.fbx new file mode 100644 index 0000000000..ad4f4e17bf Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_slownod.fbx differ diff --git a/interface/resources/avatar/animations/talk04.fbx b/interface/resources/avatar/animations/talk04.fbx new file mode 100644 index 0000000000..be2ba0a11f Binary files /dev/null and b/interface/resources/avatar/animations/talk04.fbx differ diff --git a/interface/resources/avatar/animations/talk_righthand.fbx b/interface/resources/avatar/animations/talk_righthand.fbx new file mode 100644 index 0000000000..df8849b4b4 Binary files /dev/null and b/interface/resources/avatar/animations/talk_righthand.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 8a212a16da..738975bb8c 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -197,100 +197,260 @@ "id": "rightHandStateMachine", "type": "stateMachine", "data": { - "currentState": "rightHandAnimNone", + "currentState": "rightHandGrasp", "states": [ { - "id": "rightHandAnimNone", - "interpTarget": 1, + "id": "rightHandGrasp", + "interpTarget": 3, "interpDuration": 3, "transitions": [ - { "var": "rightHandAnimA", "state": "rightHandAnimA" }, - { "var": "rightHandAnimB", "state": "rightHandAnimB" } + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } ] }, { - "id": "rightHandAnimA", - "interpTarget": 1, + "id": "rightIndexPoint", + "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "rightHandAnimNone", "state": "rightHandAnimNone" }, - { "var": "rightHandAnimB", "state": "rightHandAnimB" } + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } ] }, { - "id": "rightHandAnimB", - "interpTarget": 1, + "id": "rightThumbRaise", + "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "rightHandAnimNone", "state": "rightHandAnimNone" }, - { "var": "rightHandAnimA", "state": "rightHandAnimA" } + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" } ] } ] }, "children": [ { - "id": "rightHandAnimNone", + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", "type": "stateMachine", "data": { - "currentState": "rightHandGrasp", + "currentState": "leftHandGrasp", "states": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "interpTarget": 3, "interpDuration": 3, "transitions": [ - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } ] } ] }, "children": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightHandGraspOpen", + "id": "leftHandGraspOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -299,12 +459,12 @@ "children": [] }, { - "id": "rightHandGraspClosed", + "id": "leftHandGraspClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, "timeScale": 1.0, "loopFlag": true }, @@ -313,18 +473,18 @@ ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointOpen", + "id": "leftIndexPointOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -333,10 +493,10 @@ "children": [] }, { - "id": "rightIndexPointClosed", + "id": "leftIndexPointClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -347,18 +507,18 @@ ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightThumbRaiseOpen", + "id": "leftThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -367,10 +527,10 @@ "children": [] }, { - "id": "rightThumbRaiseClosed", + "id": "leftThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -381,18 +541,18 @@ ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointAndThumbRaiseOpen", + "id": "leftIndexPointAndThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -401,10 +561,10 @@ "children": [] }, { - "id": "rightIndexPointAndThumbRaiseClosed", + "id": "leftIndexPointAndThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -417,290 +577,6 @@ ] }, { - "id": "rightHandAnimA", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandAnimB", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" - }, - "children": [ - { - "id": "leftHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "leftHandAnimNone", - "states": [ - { - "id": "leftHandAnimNone", - "interpTarget": 1, - "interpDuration": 3, - "transitions": [ - { "var": "leftHandAnimA", "state": "leftHandAnimA" }, - { "var": "leftHandAnimB", "state": "leftHandAnimB" } - ] - }, - { - "id": "leftHandAnimA", - "interpTarget": 1, - "interpDuration": 3, - "transitions": [ - { "var": "leftHandAnimNone", "state": "leftHandAnimNone" }, - { "var": "leftHandAnimB", "state": "leftHandAnimB" } - ] - }, - { - "id": "leftHandAnimB", - "interpTarget": 1, - "interpDuration": 3, - "transitions": [ - { "var": "leftHandAnimNone", "state": "leftHandAnimNone" }, - { "var": "leftHandAnimA", "state": "leftHandAnimA" } - ] - } - ] - }, - "children": [ - { - "id": "leftHandAnimNone", - "type": "stateMachine", - "data": { - "currentState": "leftHandGrasp", - "states": [ - { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } - ] - } - ] - }, - "children": [ - { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "leftHandAnimA", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandAnimB", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { "id": "mainStateMachine", "type": "stateMachine", "data": { @@ -1027,52 +903,557 @@ "children": [ { "id": "idle", - "type": "stateMachine", + "type": "overlay", "data": { - "currentState": "idleStand", - "states": [ - { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { "var": "isTalking", "state": "idleTalk" } - ] - }, - { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { "var": "notIsTalking", "state": "idleStand" } - ] - } - ] + "alpha": 1.0, + "alphaVar": "idleOverlayAlpha", + "boneSet": "upperBody" }, "children": [ + { + "id": "idleTalk", + "type": "randomSwitchStateMachine", + "data": { + "currentState": "idleTalk1", + "triggerRandomSwitch": "idleTalkSwitch", + "randomSwitchTimeMin": 5.0, + "randomSwitchTimeMax": 10.0, + "states": [ + { + "id": "idleTalk1", + "interpTarget": 6, + "interpDuration": 15, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "idleTalk2", + "interpTarget": 6, + "interpDuration": 15, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "idleTalk3", + "interpTarget": 6, + "interpDuration": 15, + "priority": 0.33, + "resume": true, + "transitions": [] + } + ] + }, + "children": [ + { + "id": "idleTalk1", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 1.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk2", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk_righthand.fbx", + "startFrame": 1.0, + "endFrame": 501.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk3", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk04.fbx", + "startFrame": 1.0, + "endFrame": 499.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, { "id": "idleStand", - "type": "clip", + "type": "randomSwitchStateMachine", "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true + "currentState": "masterIdle", + "triggerTimeMin": 10.0, + "triggerTimeMax": 60.0, + "transitionVar": "timeToFidget", + "states": [ + { + "id": "masterIdle", + "interpTarget": 21, + "interpDuration": 20, + "priority": 1.0, + "resume": false, + "transitions": [ + { "var": "timeToFidget", "randomSwitchState": "fidget" } + ] + }, + { + "id": "fidget", + "interpTarget": 21, + "interpDuration": 20, + "priority": -1.0, + "resume": false, + "transitions": [ + { "var": "movement1OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement2OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement3OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement4OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement5OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement6OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement7OnDone", "randomSwitchState": "masterIdle" }, + { "var": "movement8OnDone", "randomSwitchState": "masterIdle" }, + { "var": "alt1ToMasterIdleOnDone", "randomSwitchState": "masterIdle" }, + { "var": "alt2ToMasterIdleOnDone", "randomSwitchState": "masterIdle" } + ] + } + ] }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] + "children": [ + { + "id": "masterIdle", + "type": "randomSwitchStateMachine", + "data": { + "currentState": "masterIdle1", + "triggerRandomSwitch": "masterIdleSwitch", + "randomSwitchTimeMin": 10.0, + "randomSwitchTimeMax": 60.0, + "states": [ + { + "id": "masterIdle1", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "masterIdle2", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "masterIdle3", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + } + ] + }, + "children": [ + { + "id": "masterIdle1", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 1.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "masterIdle2", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idleWS_all.fbx", + "startFrame": 1.0, + "endFrame": 1620.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "masterIdle3", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_lookaround01.fbx", + "startFrame": 1.0, + "endFrame": 901.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fidget", + "type": "randomSwitchStateMachine", + "data": { + "currentState": "movement", + "states": [ + { + "id": "movement", + "interpTarget": 17, + "interpDuration": 15, + "priority": 0.8, + "resume": false, + "transitions": [] + }, + { + "id": "alternateIdle", + "interpTarget": 17, + "interpDuration": 15, + "priority": 0.2, + "resume": false, + "transitions": [] + } + ] + }, + "children": [ + { + "id": "movement", + "type": "randomSwitchStateMachine", + "data": { + "currentState": "movement1", + "states": [ + { + "id": "movement1", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement2", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement3", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement4", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement5", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement6", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement7", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + }, + { + "id": "movement8", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.2, + "resume": false, + "transitions": [] + } + ] + }, + "children": [ + { + "id": "movement1", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_slownod.fbx", + "startFrame": 1, + "endFrame": 91.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement2", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_headtilt.fbx", + "startFrame": 1, + "endFrame": 154, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement3", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_headtilt.fbx", + "startFrame": 1, + "endFrame": 154, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement4", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idleWS_all.fbx", + "startFrame": 1, + "endFrame": 1620, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement5", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_lookaround.fbx", + "startFrame": 1, + "endFrame": 324, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement6", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_neckstretch.fbx", + "startFrame": 1, + "endFrame": 169, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement7", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idleWS_all.fbx", + "startFrame": 1, + "endFrame": 1620, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "movement8", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_once_lookaround.fbx", + "startFrame": 1, + "endFrame": 324, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "alternateIdle", + "type": "randomSwitchStateMachine", + "data": { + "currentState": "transitionToAltIdle1", + "triggerTimeMin": 10.0, + "triggerTimeMax": 60.0, + "transitionVar": "finishAltIdle2", + "states": [ + { + "id": "transitionToAltIdle1", + "interpTarget": 11, + "interpDuration": 10, + "priority": 0.5, + "resume": false, + "transitions": [ + { + "var": "transitionToAltIdle1OnDone", + "randomSwitchState": "altIdle1" + } + ] + }, + { + "id": "transitionToAltIdle2", + "interpTarget": 11, + "interpDuration": 10, + "priority": 0.5, + "resume": false, + "transitions": [ + { + "var": "transitionToAltIdle2OnDone", + "randomSwitchState": "altIdle2" + } + ] + }, + { + "id": "altIdle1", + "interpTarget": 11, + "interpDuration": 10, + "priority": -1.0, + "resume": false, + "transitions": [ + { + "var": "finishAltIdle2", + "randomSwitchState": "alt1ToMasterIdle" + } + ] + }, + { + "id": "altIdle2", + "interpTarget": 11, + "interpDuration": 10, + "priority": -1.0, + "resume": false, + "transitions": [ + { + "var": "finishAltIdle2", + "randomSwitchState": "alt2ToMasterIdle" + } + ] + }, + { + "id": "alt1ToMasterIdle", + "interpTarget": 11, + "interpDuration": 10, + "priority": -1.0, + "resume": false, + "transitions": [] + }, + { + "id": "alt2ToMasterIdle", + "interpTarget": 11, + "interpDuration": 10, + "priority": -1.0, + "resume": false, + "transitions": [] + } + ] + }, + "children": [ + { + "id": "transitionToAltIdle1", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_LFF_all.fbx", + "startFrame": 1, + "endFrame": 55, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "transitionToAltIdle2", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_RFF_all.fbx", + "startFrame": 1, + "endFrame": 56, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "altIdle1", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_LFF_all.fbx", + "startFrame": 55, + "endFrame": 389, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "altIdle2", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_RFF_all.fbx", + "startFrame": 56, + "endFrame": 390, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "alt1ToMasterIdle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_LFF_all.fbx", + "startFrame": 389, + "endFrame": 472, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "alt2ToMasterIdle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_RFF_all.fbx", + "startFrame": 390, + "endFrame": 453, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + } + + ] + } + + ] } ] }, diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp index c8efd83318..186a88504f 100644 --- a/libraries/animation/src/AnimContext.cpp +++ b/libraries/animation/src/AnimContext.cpp @@ -11,11 +11,12 @@ #include "AnimContext.h" AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, - const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) : + const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount) : _enableDebugDrawIKTargets(enableDebugDrawIKTargets), _enableDebugDrawIKConstraints(enableDebugDrawIKConstraints), _enableDebugDrawIKChains(enableDebugDrawIKChains), _geometryToRigMatrix(geometryToRigMatrix), - _rigToWorldMatrix(rigToWorldMatrix) + _rigToWorldMatrix(rigToWorldMatrix), + _evaluationCount(evaluationCount) { } diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e3ab5d9788..5f353fcae4 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -24,6 +24,7 @@ enum class AnimNodeType { BlendLinearMove, Overlay, StateMachine, + RandomSwitchStateMachine, Manipulator, InverseKinematics, DefaultPose, @@ -37,13 +38,14 @@ class AnimContext { public: AnimContext() {} AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, - const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); + const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount); bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; } bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; } bool getEnableDebugDrawIKChains() const { return _enableDebugDrawIKChains; } const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } + int getEvaluationCount() const { return _evaluationCount; } float getDebugAlpha(const QString& key) const { auto it = _debugAlphaMap.find(key); @@ -85,6 +87,7 @@ protected: bool _enableDebugDrawIKChains { false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; + int _evaluationCount{ 0 }; // used for debugging internal state of animation system. mutable DebugAlphaMap _debugAlphaMap; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 1a12bb8ddb..31e10ca2d5 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -43,6 +43,7 @@ public: friend class AnimDebugDraw; friend void buildChildMap(std::map& map, Pointer node); friend class AnimStateMachine; + friend class AnimRandomSwitch; AnimNode(Type type, const QString& id) : _type(type), _id(id) {} virtual ~AnimNode() {} diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index b637d131f8..4131009324 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -22,6 +22,7 @@ #include "AnimationLogging.h" #include "AnimOverlay.h" #include "AnimStateMachine.h" +#include "AnimRandomSwitch.h" #include "AnimManipulator.h" #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" @@ -38,6 +39,7 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q 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); @@ -51,6 +53,7 @@ static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; // 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); static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { @@ -59,6 +62,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { 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"; @@ -92,6 +96,16 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) { } } +static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) { + if (str == "snapshotBoth") { + return AnimRandomSwitch::InterpType::SnapshotBoth; + } else if (str == "snapshotPrev") { + return AnimRandomSwitch::InterpType::SnapshotPrev; + } else { + return AnimRandomSwitch::InterpType::NumTypes; + } +} + static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::Absolute: return "absolute"; @@ -122,6 +136,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { 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; @@ -140,6 +155,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { 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; @@ -463,6 +479,11 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const return node; } +static AnimNode::Pointer loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + auto node = std::make_shared(id); + return node; +} + static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); @@ -780,6 +801,141 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, return true; } +bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { + auto smNode = std::static_pointer_cast(node); + assert(smNode); + + READ_STRING(currentState, jsonObj, nodeId, jsonUrl, false); + READ_OPTIONAL_FLOAT(randomSwitchTimeMin, jsonObj, -1.0f); + READ_OPTIONAL_FLOAT(randomSwitchTimeMax, jsonObj, -1.0f); + READ_OPTIONAL_STRING(triggerRandomSwitch, jsonObj); + READ_OPTIONAL_FLOAT(triggerTimeMin, jsonObj, -1.0f); + READ_OPTIONAL_FLOAT(triggerTimeMax, jsonObj, -1.0f); + READ_OPTIONAL_STRING(transitionVar, jsonObj); + + + + auto statesValue = jsonObj.value("states"); + if (!statesValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in random switch state Machine node, id =" << nodeId; + return false; + } + + // build a map for all children by name. + std::map childMap; + buildChildMap(childMap, node); + + // first pass parse all the states and build up the state and transition map. + using StringPair = std::pair; + using TransitionMap = std::multimap; + TransitionMap transitionMap; + + using RandomStateMap = std::map; + RandomStateMap randomStateMap; + + auto randomStatesArray = statesValue.toArray(); + for (const auto& randomStateValue : randomStatesArray) { + if (!randomStateValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad state object in \"random states\", id =" << nodeId; + return false; + } + auto stateObj = randomStateValue.toObject(); + + 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_FLOAT(priority, stateObj, nodeId, jsonUrl, false); + READ_BOOL(resume, stateObj, nodeId, jsonUrl, false); + + READ_OPTIONAL_STRING(interpTargetVar, stateObj); + READ_OPTIONAL_STRING(interpDurationVar, stateObj); + READ_OPTIONAL_STRING(interpTypeVar, stateObj); + + auto iter = childMap.find(id); + if (iter == childMap.end()) { + qCCritical(animation) << "AnimNodeLoader, could not find random stateMachine child (state) with nodeId =" << nodeId << "random stateId =" << id; + return false; + } + + AnimRandomSwitch::InterpType interpTypeEnum = AnimRandomSwitch::InterpType::SnapshotPrev; // default value + if (!interpType.isEmpty()) { + interpTypeEnum = stringToRandomInterpType(interpType); + if (interpTypeEnum == AnimRandomSwitch::InterpType::NumTypes) { + qCCritical(animation) << "AnimNodeLoader, bad interpType on random state Machine state, nodeId = " << nodeId << "random stateId =" << id; + return false; + } + } + + auto randomStatePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum, priority, resume); + if (priority > 0.0f) { + smNode->addToPrioritySum(priority); + } + assert(randomStatePtr); + + if (!interpTargetVar.isEmpty()) { + randomStatePtr->setInterpTargetVar(interpTargetVar); + } + if (!interpDurationVar.isEmpty()) { + randomStatePtr->setInterpDurationVar(interpDurationVar); + } + if (!interpTypeVar.isEmpty()) { + randomStatePtr->setInterpTypeVar(interpTypeVar); + } + + smNode->addState(randomStatePtr); + randomStateMap.insert(RandomStateMap::value_type(randomStatePtr->getID(), randomStatePtr)); + + auto transitionsValue = stateObj.value("transitions"); + if (!transitionsValue.isArray()) { + qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in random state Machine node, stateId =" << id << "nodeId =" << nodeId; + return false; + } + + auto transitionsArray = transitionsValue.toArray(); + for (const auto& transitionValue : transitionsArray) { + if (!transitionValue.isObject()) { + qCritical(animation) << "AnimNodeLoader, bad transition object in \"transitions\", random stateId =" << id << "nodeId =" << nodeId; + return false; + } + auto transitionObj = transitionValue.toObject(); + + READ_STRING(var, transitionObj, nodeId, jsonUrl, false); + READ_STRING(randomSwitchState, transitionObj, nodeId, jsonUrl, false); + + transitionMap.insert(TransitionMap::value_type(randomStatePtr, StringPair(var, randomSwitchState))); + } + } + + // second pass: now iterate thru all transitions and add them to the appropriate states. + for (auto& transition : transitionMap) { + AnimRandomSwitch::RandomSwitchState::Pointer srcState = transition.first; + auto iter = randomStateMap.find(transition.second.second); + if (iter != randomStateMap.end()) { + srcState->addTransition(AnimRandomSwitch::RandomSwitchState::Transition(transition.second.first, iter->second)); + } else { + qCCritical(animation) << "AnimNodeLoader, bad random state machine transition from srcState =" << srcState->_id << "dstState =" << transition.second.second << "nodeId =" << nodeId; + return false; + } + } + + auto iter = randomStateMap.find(currentState); + if (iter == randomStateMap.end()) { + qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId; + } + smNode->setCurrentState(iter->second); + smNode->setRandomSwitchTimeMin(randomSwitchTimeMin); + smNode->setRandomSwitchTimeMax(randomSwitchTimeMax); + smNode->setTriggerRandomSwitchVar(triggerRandomSwitch); + smNode->setTriggerTimeMin(triggerTimeMin); + smNode->setTriggerTimeMax(triggerTimeMax); + smNode->setTransitionVar(transitionVar); + + return true; +} + + + AnimNodeLoader::AnimNodeLoader(const QUrl& url) : _url(url) { diff --git a/libraries/animation/src/AnimRandomSwitch.cpp b/libraries/animation/src/AnimRandomSwitch.cpp new file mode 100644 index 0000000000..2549a50062 --- /dev/null +++ b/libraries/animation/src/AnimRandomSwitch.cpp @@ -0,0 +1,212 @@ +// +// AnimRandomSwitch.cpp +// +// Created by Angus Antley on 4/8/2019. +// Copyright (c) 2019 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 "AnimRandomSwitch.h" +#include "AnimUtil.h" +#include "AnimationLogging.h" + +AnimRandomSwitch::AnimRandomSwitch(const QString& id) : + AnimNode(AnimNode::Type::RandomSwitchStateMachine, id) { + +} + +AnimRandomSwitch::~AnimRandomSwitch() { + +} + +const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + float parentDebugAlpha = context.getDebugAlpha(_id); + + AnimRandomSwitch::RandomSwitchState::Pointer desiredState = _currentState; + if (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1 || animVars.lookup(_triggerRandomSwitchVar, false)) { + + // get a random number and decide which motion to choose. + bool currentStateHasPriority = false; + float dice = randFloatInRange(0.0f, 1.0f); + float lowerBound = 0.0f; + for (const RandomSwitchState::Pointer& randState : _randomStates) { + if (randState->getPriority() > 0.0f) { + float upperBound = lowerBound + (randState->getPriority() / _totalPriorities); + if ((dice > lowerBound) && (dice < upperBound)) { + desiredState = randState; + } + lowerBound = upperBound; + + // this indicates if the curent state is one that can be selected randomly, or is one that was transitioned to by the random duration timer. + currentStateHasPriority = currentStateHasPriority || (_currentState == randState); + } + } + if (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1) { + _duringInterp = false; + switchRandomState(animVars, context, desiredState, _duringInterp); + } else { + // firing a random switch, be sure that we aren't completing a previously triggered transition + if (currentStateHasPriority) { + if (desiredState->getID() != _currentState->getID()) { + _duringInterp = true; + switchRandomState(animVars, context, desiredState, _duringInterp); + } else { + _duringInterp = false; + } + } + } + _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); + _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); + + } else { + + // here we are checking to see if we want a temporary movement + // evaluate currentState transitions + auto transitionState = evaluateTransitions(animVars); + if (transitionState != _currentState) { + _duringInterp = true; + switchRandomState(animVars, context, transitionState, _duringInterp); + _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); + _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); + } + } + + _triggerTime -= dt; + if ((_triggerTime < 0.0f) && (_triggerTimeMin > 0.0f) && (_triggerTimeMax > 0.0f)) { + _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); + triggersOut.setTrigger(_transitionVar); + } + + _randomSwitchTime -= dt; + if ((_randomSwitchTime < 0.0f) && (_randomSwitchTimeMin > 0.0f) && (_randomSwitchTimeMax > 0.0f)) { + _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); + // restart the trigger timer if it is also enabled + _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); + triggersOut.setTrigger(_triggerRandomSwitchVar); + } + + assert(_currentState); + auto currentStateNode = _children[_currentState->getChildIndex()]; + assert(currentStateNode); + + if (_duringInterp) { + _alpha += _alphaVel * dt; + if (_alpha < 1.0f) { + 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, context, 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]); + } + context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); + } else { + _duringInterp = false; + _prevPoses.clear(); + _nextPoses.clear(); + } + } + + if (!_duringInterp){ + context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); + _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); + } + + _randomSwitchEvaluationCount = context.getEvaluationCount(); + processOutputJoints(triggersOut); + + context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); + if (_duringInterp) { + // hack: add previoius state to debug alpha map, with parens around it's name. + context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip); + } + + return _poses; +} + +void AnimRandomSwitch::setCurrentState(RandomSwitchState::Pointer randomState) { + _previousState = _currentState ? _currentState : randomState; + _currentState = randomState; +} + +void AnimRandomSwitch::addState(RandomSwitchState::Pointer randomState) { + _randomStates.push_back(randomState); +} + +void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp) { + + auto nextStateNode = _children[desiredState->getChildIndex()]; + if (shouldInterp) { + + const float FRAMES_PER_SECOND = 30.0f; + + auto prevStateNode = _children[_currentState->getChildIndex()]; + + _alpha = 0.0f; + float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); + _alphaVel = FRAMES_PER_SECOND / duration; + _interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType); + + // because dt is 0, we should not encounter any triggers + const float dt = 0.0f; + AnimVariantMap triggers; + + if (_interpType == InterpType::SnapshotBoth) { + // snapshot previous pose. + _prevPoses = _poses; + // snapshot next pose at the target frame. + if (!desiredState->getResume()) { + nextStateNode->setCurrentFrame(desiredState->_interpTarget); + } + _nextPoses = nextStateNode->evaluate(animVars, context, 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. + if (!desiredState->getResume()) { + nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration); + } + } else { + assert(false); + } + } else { + if (!desiredState->getResume()) { + nextStateNode->setCurrentFrame(desiredState->_interpTarget); + } + } + +#ifdef WANT_DEBUG + qCDebug(animation) << "AnimRandomSwitch::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; +#endif + + setCurrentState(desiredState); +} + +AnimRandomSwitch::RandomSwitchState::Pointer AnimRandomSwitch::evaluateTransitions(const AnimVariantMap& animVars) const { + assert(_currentState); + for (auto& transition : _currentState->_transitions) { + if (animVars.lookup(transition._var, false)) { + return transition._randomSwitchState; + } + } + return _currentState; +} + +const AnimPoseVec& AnimRandomSwitch::getPosesInternal() const { + return _poses; +} diff --git a/libraries/animation/src/AnimRandomSwitch.h b/libraries/animation/src/AnimRandomSwitch.h new file mode 100644 index 0000000000..7a750cd89f --- /dev/null +++ b/libraries/animation/src/AnimRandomSwitch.h @@ -0,0 +1,184 @@ +// +// AnimRandomSwitch.h +// +// Created by Angus Antley on 4/8/19. +// Copyright (c) 2019 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_AnimRandomSwitch_h +#define hifi_AnimRandomSwitch_h + +#include +#include +#include "AnimNode.h" + +// Random Switch State Machine for random transitioning between children AnimNodes +// +// This is mechanisim for choosing and playing a random animation and smoothly interpolating/fading +// between them. A RandomSwitch has a set of States, which typically reference +// child AnimNodes. Each Random Switch State has a list of Transitions, which are evaluated +// to determine when we should switch to a new State. Parameters for the smooth +// interpolation/fading are read from the Random Switch State that you are transitioning to. +// +// The currentState can be set directly via the setCurrentStateVar() and will override +// any State transitions. +// +// Each Random Switch State has two parameters that can be changed via AnimVars, +// * interpTarget - (frames) The destination frame of the interpolation. i.e. the first frame of the animation that will +// 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. +// * priority - this number represents how likely this Random Switch State will be chosen. +// the priority for each Random Switch State will be normalized, so their relative size is what is important +// * resume - if resume is false then if this state is chosen twice in a row it will remember what frame it was playing on. +// * 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 AnimRandomSwitch : public AnimNode { +public: + friend class AnimNodeLoader; + friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + + enum class InterpType { + SnapshotBoth = 0, + SnapshotPrev, + NumTypes + }; + +protected: + + class RandomSwitchState { + public: + friend AnimRandomSwitch; + friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + + using Pointer = std::shared_ptr; + using ConstPointer = std::shared_ptr; + + class Transition { + public: + friend AnimRandomSwitch; + Transition(const QString& var, RandomSwitchState::Pointer randomState) : _var(var), _randomSwitchState(randomState) {} + protected: + QString _var; + RandomSwitchState::Pointer _randomSwitchState; + }; + + RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, float priority, bool resume) : + _id(id), + _childIndex(childIndex), + _interpTarget(interpTarget), + _interpDuration(interpDuration), + _interpType(interpType), + _priority(priority), + _resume(resume){ + } + + 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; } + float getPriority() const { return _priority; } + bool getResume() const { return _resume; } + const QString& getID() const { return _id; } + + protected: + + void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; } + void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; } + void setPriority(float priority) { _priority = priority; } + void setResumeFlag(bool resume) { _resume = resume; } + + void addTransition(const Transition& transition) { _transitions.push_back(transition); } + + QString _id; + int _childIndex; + float _interpTarget; // frames + float _interpDuration; // frames + InterpType _interpType; + float _priority {0.0f}; + bool _resume {false}; + + QString _interpTargetVar; + QString _interpDurationVar; + QString _interpTypeVar; + + std::vector _transitions; + + private: + // no copies + RandomSwitchState(const RandomSwitchState&) = delete; + RandomSwitchState& operator=(const RandomSwitchState&) = delete; + }; + +public: + + explicit AnimRandomSwitch(const QString& id); + virtual ~AnimRandomSwitch() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + + void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; } + +protected: + + void setCurrentState(RandomSwitchState::Pointer randomState); + void setTriggerRandomSwitchVar(const QString& triggerRandomSwitchVar) { _triggerRandomSwitchVar = triggerRandomSwitchVar; } + void setRandomSwitchTimeMin(float randomSwitchTimeMin) { _randomSwitchTimeMin = randomSwitchTimeMin; } + void setRandomSwitchTimeMax(float randomSwitchTimeMax) { _randomSwitchTimeMax = randomSwitchTimeMax; } + void setTransitionVar(const QString& transitionVar) { _transitionVar = transitionVar; } + void setTriggerTimeMin(float triggerTimeMin) { _triggerTimeMin = triggerTimeMin; } + void setTriggerTimeMax(float triggerTimeMax) { _triggerTimeMax = triggerTimeMax; } + void addToPrioritySum(float priority) { _totalPriorities += priority; } + + void addState(RandomSwitchState::Pointer randomState); + + void switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp); + RandomSwitchState::Pointer evaluateTransitions(const AnimVariantMap& animVars) const; + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + AnimPoseVec _poses; + + int _randomSwitchEvaluationCount { 0 }; + // interpolation state + bool _duringInterp = false; + InterpType _interpType{ InterpType::SnapshotPrev }; + float _alphaVel = 0.0f; + float _alpha = 0.0f; + AnimPoseVec _prevPoses; + AnimPoseVec _nextPoses; + float _totalPriorities { 0.0f }; + + RandomSwitchState::Pointer _currentState; + RandomSwitchState::Pointer _previousState; + std::vector _randomStates; + + QString _currentStateVar; + QString _triggerRandomSwitchVar; + QString _transitionVar; + float _triggerTimeMin { 10.0f }; + float _triggerTimeMax { 20.0f }; + float _triggerTime { 0.0f }; + float _randomSwitchTimeMin { 10.0f }; + float _randomSwitchTimeMax { 20.0f }; + float _randomSwitchTime { 0.0f }; + +private: + // no copies + AnimRandomSwitch(const AnimRandomSwitch&) = delete; + AnimRandomSwitch& operator=(const AnimRandomSwitch&) = delete; +}; + +#endif // hifi_AnimRandomSwitch_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a4c57025be..633a505d14 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1480,13 +1480,15 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons if (_animNode && _enabledAnimations) { DETAILED_PERFORMANCE_TIMER("handleTriggers"); + ++_evaluationCount; + updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); if (_networkNode) { _networkVars.setRigToGeometryTransform(_rigToGeometryTransform); } AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, - getGeometryToRigTransform(), rigToWorldTransform); + getGeometryToRigTransform(), rigToWorldTransform, _evaluationCount); // evaluate the animation AnimVariantMap triggersOut; @@ -2009,8 +2011,35 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo return; } - _animVars.set("isTalking", params.isTalking); - _animVars.set("notIsTalking", !params.isTalking); + if (_previousIsTalking != params.isTalking) { + if (_talkIdleInterpTime < 1.0f) { + _talkIdleInterpTime = 1.0f - _talkIdleInterpTime; + } else { + _talkIdleInterpTime = 0.0f; + } + } + _previousIsTalking = params.isTalking; + + const float TOTAL_EASE_IN_TIME = 0.75f; + const float TOTAL_EASE_OUT_TIME = 1.5f; + if (params.isTalking) { + if (_talkIdleInterpTime < 1.0f) { + _talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME; + float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; + _animVars.set("idleOverlayAlpha", easeOutInValue); + } else { + _animVars.set("idleOverlayAlpha", 1.0f); + } + } else { + if (_talkIdleInterpTime < 1.0f) { + _talkIdleInterpTime += dt / TOTAL_EASE_OUT_TIME; + float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; + float talkAlpha = 1.0f - easeOutInValue; + _animVars.set("idleOverlayAlpha", talkAlpha); + } else { + _animVars.set("idleOverlayAlpha", 0.0f); + } + } _headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled; bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index b9a7f73117..786d14200e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -418,9 +418,12 @@ protected: HandAnimState _rightHandAnimState; HandAnimState _leftHandAnimState; std::map _roleAnimStates; + int _evaluationCount{ 0 }; float _leftHandOverlayAlpha { 0.0f }; float _rightHandOverlayAlpha { 0.0f }; + float _talkIdleInterpTime { 0.0f }; + bool _previousIsTalking { false }; SimpleMovingAverage _averageForwardSpeed { 10 }; SimpleMovingAverage _averageLateralSpeed { 10 }; diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 708b1b2d2a..f217847a5f 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -94,7 +94,7 @@ void makeTestFBXJoints(HFMModel& hfmModel) { void AnimInverseKinematicsTests::testSingleChain() { - AnimContext context(false, false, false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0); HFMModel hfmModel; makeTestFBXJoints(hfmModel); diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index a14ffcf967..8a30ba8f56 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -72,7 +72,7 @@ static float framesToSec(float secs) { } void AnimTests::testClipEvaulate() { - AnimContext context(false, false, false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0); QString id = "myClipNode"; QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; @@ -109,7 +109,7 @@ void AnimTests::testClipEvaulate() { } void AnimTests::testClipEvaulateWithVars() { - AnimContext context(false, false, false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0); QString id = "myClipNode"; QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f;