78 lines
9.3 KiB
C++
78 lines
9.3 KiB
C++
#include "MetaCoreAnimation/MetaCoreAnimation.h"
|
|
#include "MetaCoreScene/MetaCoreScene.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
|
|
namespace {
|
|
void Expect(bool value, const char* message) { if (!value) { std::cerr << "MetaCoreAnimationTests: " << message << '\n'; std::exit(1); } }
|
|
bool Near(float a,float b,float e=.001F){return std::abs(a-b)<=e;}
|
|
}
|
|
|
|
int main() {
|
|
using namespace MetaCore;
|
|
const auto clipGuid=MetaCoreBuildDeterministicAnimationGuid("clip/walk");
|
|
const auto controllerGuid=MetaCoreBuildDeterministicAnimationGuid("controller/test");
|
|
const auto timelineGuid=MetaCoreBuildDeterministicAnimationGuid("timeline/test");
|
|
|
|
MetaCoreAnimationClipDocument clip;clip.AssetGuid=clipGuid;clip.Name="Walk";clip.Duration=1;clip.RootNodePath="Root";
|
|
MetaCoreAnimationChannelDocument channel;channel.NodePath="Root";channel.Target=MetaCoreAnimationChannelTarget::Translation;
|
|
channel.Vec3Keys={{0,{0,0,0}},{1,{10,0,0}}};clip.Channels.push_back(channel);
|
|
clip.Events.push_back({"half",.5F,"Footstep"});
|
|
Expect(MetaCoreValidateAnimationClip(clip),"valid clip rejected");
|
|
Expect(Near(MetaCoreSampleAnimationClip(clip,.25F).Nodes.at("Root").Translation.x,2.5F),"linear clip sampling failed");
|
|
Expect(Near(MetaCoreSampleAnimationClip(clip,1.0F).Nodes.at("Root").Translation.x,10.0F),"clip end pose was not held");
|
|
channel.Interpolation=MetaCoreAnimationInterpolation::Step;Expect(Near(MetaCoreSampleAnimationVec3(channel.Vec3Keys,.75F,channel.Interpolation).x,0),"step sampling failed");
|
|
|
|
MetaCoreAnimatorControllerDocument controller;controller.AssetGuid=controllerGuid;
|
|
controller.Parameters.push_back({"Run",MetaCoreAnimatorParameterType::Trigger});
|
|
MetaCoreAnimatorStateDocument idle;idle.Id="Idle";idle.Motion.ClipGuid=clipGuid;
|
|
MetaCoreAnimatorStateDocument run=idle;run.Id="Run";
|
|
MetaCoreAnimatorLayerDocument layer;layer.DefaultStateId="Idle";layer.States={idle,run};
|
|
MetaCoreAnimatorTransitionDocument transition;transition.Id="to-run";transition.SourceStateId="Idle";transition.TargetStateId="Run";transition.Conditions.push_back({"Run",MetaCoreAnimatorConditionMode::If});layer.Transitions.push_back(transition);controller.Layers.push_back(layer);
|
|
Expect(MetaCoreValidateAnimatorController(controller),"valid controller rejected");
|
|
|
|
MetaCoreTimelineDocument timeline;timeline.AssetGuid=timelineGuid;timeline.Duration=1;
|
|
MetaCoreTimelineTrackDocument track;track.Id="move";track.BindingSlot="Part";track.Type=MetaCoreTimelineTrackType::Transform;
|
|
MetaCoreTimelineKeyDocument k0,k1;k1.Time=1;k1.Transform.Translation={4,0,0};track.Keys={k0,k1};timeline.Tracks.push_back(track);
|
|
MetaCoreTimelineTrackDocument signal;signal.Id="signal";signal.BindingSlot="Part";signal.Type=MetaCoreTimelineTrackType::Signal;MetaCoreTimelineKeyDocument signalKey;signalKey.Time=.25F;signalKey.TextValue="ClampClosed";signal.Keys.push_back(signalKey);timeline.Tracks.push_back(signal);
|
|
Expect(MetaCoreValidateTimeline(timeline),"valid timeline rejected");
|
|
|
|
MetaCoreAvatarDocument runtimeAvatar;runtimeAvatar.AssetGuid=MetaCoreBuildDeterministicAnimationGuid("avatar/runtime");runtimeAvatar.RootBonePath="Target";runtimeAvatar.UniformScale=1.0F;
|
|
MetaCoreAvatarBoneDocument sourceBone;sourceBone.Path="Root";runtimeAvatar.Bones.push_back(sourceBone);
|
|
MetaCoreAvatarBoneDocument targetBone;targetBone.Path="Target";targetBone.BindPose.Translation={2,0,0};runtimeAvatar.Bones.push_back(targetBone);
|
|
runtimeAvatar.Mappings.push_back({"Root","Target"});Expect(MetaCoreValidateAvatar(runtimeAvatar),"runtime avatar rejected");
|
|
|
|
const auto temp=std::filesystem::temp_directory_path()/"metacore_animation_tests";std::error_code ec;std::filesystem::create_directories(temp,ec);
|
|
Expect(MetaCoreWriteAnimationClip(temp/"Walk.mcanim",clip)&&MetaCoreReadAnimationClip(temp/"Walk.mcanim").has_value(),"clip round trip failed");
|
|
Expect(MetaCoreWriteAnimatorController(temp/"Test.mcanimator",controller)&&MetaCoreReadAnimatorController(temp/"Test.mcanimator").has_value(),"controller round trip failed");
|
|
Expect(MetaCoreWriteTimeline(temp/"Test.mctimeline",timeline)&&MetaCoreReadTimeline(temp/"Test.mctimeline").has_value(),"timeline round trip failed");
|
|
|
|
MetaCoreScene scene;auto animated=scene.CreateGameObject("Animated");MetaCoreAnimatorComponent animator;animator.ControllerAssetGuid=controllerGuid;animator.AvatarAssetGuid=runtimeAvatar.AssetGuid;animator.RootMotionMode=MetaCoreRootMotionMode::ExtractOnly;animated.AddComponent<MetaCoreAnimatorComponent>(animator);
|
|
auto part=scene.CreateGameObject("Part");auto directorObject=scene.CreateGameObject("Director");MetaCoreTimelineDirectorComponent director;director.TimelineAssetGuid=timelineGuid;director.Bindings.push_back({"Part",part.GetId()});directorObject.AddComponent<MetaCoreTimelineDirectorComponent>(director);
|
|
int eventCount=0;MetaCoreAnimationRuntime runtime(scene,[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimationClipDocument>{return guid==clipGuid?std::optional(clip):std::nullopt;},[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimatorControllerDocument>{return guid==controllerGuid?std::optional(controller):std::nullopt;},[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAvatarDocument>{return guid==runtimeAvatar.AssetGuid?std::optional(runtimeAvatar):std::nullopt;},[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreTimelineDocument>{return guid==timelineGuid?std::optional(timeline):std::nullopt;});
|
|
runtime.SetEventCallback([&](const auto&){++eventCount;});runtime.Start();runtime.Update(.5);
|
|
Expect(runtime.GetPose(animated.GetId())!=nullptr&&Near(runtime.GetPose(animated.GetId())->Nodes.at("Root").Translation.x,5),"runtime pose failed");
|
|
Expect(Near(runtime.GetPose(animated.GetId())->Nodes.at("Target").Translation.x,7),"bind-pose retargeting failed");
|
|
Expect(Near(part.GetComponent<MetaCoreTransformComponent>().Position.x,2),"timeline transform failed");
|
|
Expect(eventCount==2,"clip/timeline event dispatch failed");Expect(!runtime.SetBool(animated.GetId(),"Run",true),"trigger accepted through bool API");Expect(runtime.SetTrigger(animated.GetId(),"Run"),"trigger rejected");runtime.Update(.01);
|
|
const auto debug=runtime.GetDebugSnapshot(animated.GetId());Expect(debug&&debug->Layers.front().StateId=="Run","state transition failed");runtime.Stop();
|
|
|
|
MetaCoreScene loopScene;auto loopObject=loopScene.CreateGameObject("Loop");MetaCoreAnimatorComponent loopAnimator;loopAnimator.ControllerAssetGuid=controllerGuid;loopObject.AddComponent<MetaCoreAnimatorComponent>(loopAnimator);
|
|
MetaCoreAnimatorControllerDocument loopController=controller;loopController.Parameters.clear();loopController.Layers.front().States.resize(1);loopController.Layers.front().States.front().Id="Idle";loopController.Layers.front().DefaultStateId="Idle";loopController.Layers.front().Transitions.clear();
|
|
int loopEvents=0;MetaCoreAnimationRuntime loopRuntime(loopScene,[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimationClipDocument>{return guid==clipGuid?std::optional(clip):std::nullopt;},[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimatorControllerDocument>{return guid==controllerGuid?std::optional(loopController):std::nullopt;});loopRuntime.SetEventCallback([&](const auto&event){if(event.Type=="ClipEvent")++loopEvents;});loopRuntime.Start();loopRuntime.Update(2.6);Expect(loopEvents==3,"multi-loop event crossing was not dispatched exactly once per crossing");loopRuntime.Stop();
|
|
|
|
MetaCoreScene stoppedScene;auto stopped=stoppedScene.CreateGameObject("Stopped");MetaCoreAnimatorComponent stoppedAnimator;stoppedAnimator.ControllerAssetGuid=controllerGuid;stoppedAnimator.PlayOnStart=false;stopped.AddComponent<MetaCoreAnimatorComponent>(stoppedAnimator);MetaCoreAnimationRuntime stoppedRuntime(stoppedScene,[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimationClipDocument>{return guid==clipGuid?std::optional(clip):std::nullopt;},[&](MetaCoreAssetGuid guid)->std::optional<MetaCoreAnimatorControllerDocument>{return guid==controllerGuid?std::optional(loopController):std::nullopt;});stoppedRuntime.Start();stoppedRuntime.Update(.25);Expect(stoppedRuntime.GetPose(stopped.GetId())&&stoppedRuntime.GetPose(stopped.GetId())->Nodes.empty(),"PlayOnStart=false advanced pose");Expect(stoppedRuntime.Play(stopped.GetId(),"Idle"),"manual Play failed");stoppedRuntime.Update(.25);Expect(!stoppedRuntime.GetPose(stopped.GetId())->Nodes.empty(),"manual Play did not start animator");stoppedRuntime.Stop();
|
|
|
|
MetaCoreAvatarDocument avatar;avatar.AssetGuid=MetaCoreBuildDeterministicAnimationGuid("avatar");avatar.RootBonePath="Root";avatar.Bones.push_back({"Root",-1});
|
|
Expect(MetaCoreValidateAvatar(avatar),"valid generic avatar rejected");avatar.Bones.push_back({"Root",0});Expect(!MetaCoreValidateAvatar(avatar),"duplicate/cyclic avatar accepted");
|
|
MetaCoreAvatarDocument unorderedAvatar;unorderedAvatar.AssetGuid=MetaCoreBuildDeterministicAnimationGuid("avatar/unordered");unorderedAvatar.RootBonePath="Root";
|
|
unorderedAvatar.Bones.push_back({"Root/Child",1});unorderedAvatar.Bones.push_back({"Root",-1});
|
|
Expect(MetaCoreValidateAvatar(unorderedAvatar),"valid glTF child-before-parent ordering rejected");
|
|
unorderedAvatar.Bones[1].Parent=0;Expect(!MetaCoreValidateAvatar(unorderedAvatar),"cyclic avatar hierarchy accepted");
|
|
std::filesystem::remove_all(temp,ec);
|
|
std::cout<<"MetaCoreAnimationTests passed\n";return 0;
|
|
}
|