// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "ModelInstanceActor.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Assets/AnimationGraph.h"
#include "Engine/Graphics/Models/SkinnedMeshDrawData.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Core/Delegate.h"
///
/// Performs an animation and renders a skinned model.
///
API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
friend class AnimationsSystem;
public:
///
/// Describes the animation graph updates frequency for the animated model.
///
API_ENUM() enum class AnimationUpdateMode
{
///
/// The automatic updates will be used (based on platform capabilities, distance to the player, etc.).
///
Auto = 0,
///
/// Animation will be updated every game update.
///
EveryUpdate = 1,
///
/// Animation will be updated every second game update.
///
EverySecondUpdate = 2,
///
/// Animation will be updated every fourth game update.
///
EveryFourthUpdate = 3,
///
/// Animation can be updated manually by the user scripts.
///
Manual = 4,
///
/// Animation won't be updated at all.
///
Never = 5,
};
private:
BoundingBox _boxLocal;
Matrix _world;
GeometryDrawStateData _drawState;
SkinnedMeshDrawData _skinningData;
AnimationUpdateMode _actualMode;
uint32 _counter;
float _lastMinDstSqr;
uint64 _lastUpdateFrame;
BlendShapesInstance _blendShapes;
ScriptingObjectReference _masterPose;
public:
///
/// The skinned model asset used for rendering.
///
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
AssetReference SkinnedModel;
///
/// The animation graph asset used for the skinned mesh skeleton bones evaluation (controls the animation).
///
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
AssetReference AnimationGraph;
///
/// If true, use per-bone motion blur on this skeletal model. It requires additional rendering, can be disabled to save performance.
///
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
bool PerBoneMotionBlur = true;
///
/// If true, animation speed will be affected by the global time scale parameter.
///
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
bool UseTimeScale = true;
///
/// If true, the animation will be updated even when an actor cannot be seen by any camera. Otherwise, the animations themselves will also stop running when the actor is off-screen.
///
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Skinned Model\")")
bool UpdateWhenOffscreen = false;
///
/// The animation update delta time scale. Can be used to speed up animation playback or create slow motion effect.
///
API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Skinned Model\")")
float UpdateSpeed = 1.0f;
///
/// The animation update mode. Can be used to optimize the performance.
///
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Skinned Model\")")
AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto;
///
/// The master scale parameter for the actor bounding box. Helps reducing mesh flickering effect on screen edges.
///
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0), EditorDisplay(\"Skinned Model\")")
float BoundsScale = 1.5f;
///
/// The custom bounds(in actor local space). If set to empty bounds then source skinned model bind pose bounds will be used.
///
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Skinned Model\")")
BoundingBox CustomBounds = BoundingBox::Zero;
///
/// The model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
///
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"LOD Bias\")")
int32 LODBias = 0;
///
/// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
///
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"Forced LOD\")")
int32 ForcedLOD = -1;
///
/// The draw passes to use for rendering this object.
///
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Skinned Model\")")
DrawPass DrawModes = DrawPass::Default;
///
/// The shadows casting mode.
///
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")")
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
///
/// The animation root motion apply target. If not specified the animated model will apply it itself.
///
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
ScriptingObjectReference RootMotionTarget;
public:
///
/// The graph instance data container. For dynamic usage only at runtime, not serialized.
///
AnimGraphInstanceData GraphInstance;
///
/// Gets the model world matrix transform.
///
FORCE_INLINE void GetWorld(Matrix* world) const
{
*world = _world;
}
///
/// Resets the animation state (clears the instance state data but preserves the instance parameters values).
///
API_FUNCTION() void ResetAnimation();
///
/// Performs the full animation update. The actual update will be performed during gameplay tick.
///
API_FUNCTION() void UpdateAnimation();
///
/// Called after animation gets updated (new skeleton pose).
///
API_EVENT() Action AnimationUpdated;
///
/// Validates and creates a proper skinning data.
///
API_FUNCTION() void SetupSkinningData();
///
/// Creates and setups the skinning data (writes the identity bones transformations).
///
API_FUNCTION() void PreInitSkinningData();
///
/// Gets the per-node final transformations (skeleton pose).
///
/// The output per-node final transformation matrices.
/// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.
API_FUNCTION() void GetCurrentPose(API_PARAM(Out) Array& nodesTransformation, bool worldSpace = false) const;
///
/// Sets the per-node final transformations (skeleton pose).
///
/// The per-node final transformation matrices.
/// True if convert matrices from world-space, otherwise values are in local-space of the actor.
API_FUNCTION() void SetCurrentPose(const Array& nodesTransformation, bool worldSpace = false);
///
/// Gets the node final transformation.
///
/// The index of the skinned model skeleton node.
/// The output final node transformation matrix.
/// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.
API_FUNCTION() void GetNodeTransformation(int32 nodeIndex, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
///
/// Gets the node final transformation.
///
/// The name of the skinned model skeleton node.
/// The output final node transformation matrix.
/// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
///
/// Finds the closest node to a given location.
///
/// The text location (in local-space of the actor or world-space depending on ).
/// True if convert input location is in world-space, otherwise it's in local-space of the actor.
/// The zero-based index of the found node. Returns -1 if skeleton is missing.
API_FUNCTION() int32 FindClosestNode(const Vector3& location, bool worldSpace = false) const;
///
/// Sets the master pose model that will be used to copy the skeleton nodes animation. Useful for modular characters.
///
/// The master pose actor to use.
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
public:
///
/// Gets the anim graph instance parameters collection.
///
API_PROPERTY() const Array& GetParameters() const
{
return GraphInstance.Parameters;
}
///
/// Gets the anim graph instance parameter by name.
///
/// The parameter name.
/// The parameters.
API_FUNCTION() AnimGraphParameter* GetParameter(const StringView& name);
///
/// Gets the anim graph instance parameter value.
///
/// The parameter name.
/// The value.
API_FUNCTION() Variant GetParameterValue(const StringView& name);
///
/// Sets the anim graph instance parameter value.
///
/// The parameter name.
/// The value to set.
API_FUNCTION() void SetParameterValue(const StringView& name, const Variant& value);
///
/// Gets the anim graph instance parameter value.
///
/// The parameter id.
/// The value.
API_FUNCTION() Variant GetParameterValue(const Guid& id);
///
/// Sets the anim graph instance parameter value.
///
/// The parameter id.
/// The value to set.
API_FUNCTION() void SetParameterValue(const Guid& id, const Variant& value);
public:
///
/// Gets the weight of the blend shape.
///
/// The blend shape name.
/// The normalized weight of the blend shape (in range -1:1).
API_FUNCTION() float GetBlendShapeWeight(const StringView& name);
///
/// Sets the weight of the blend shape.
///
/// The blend shape name.
/// The normalized weight of the blend shape (in range -1:1).
API_FUNCTION() void SetBlendShapeWeight(const StringView& name, float value);
///
/// Clears the weights of the blend shapes (disabled any used blend shapes).
///
API_FUNCTION() void ClearBlendShapeWeights();
public:
///
/// Plays the animation on the slot in Anim Graph.
///
/// The name of the slot.
/// The animation to play.
/// The playback speed.
/// The animation blending in time (in seconds). Cam be used to smooth the slot animation playback with the input pose when starting the animation.
/// The animation blending out time (in seconds). Cam be used to smooth the slot animation playback with the input pose when ending animation.
/// The amount of loops to play the animation: 0 to play once, -1 to play infinite, 1 or higher to loop once or more.
API_FUNCTION() void PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed = 1.0f, float blendInTime = 0.2f, float blendOutTime = 0.2f, int32 loopCount = 0);
///
/// Stops all the animations playback on the all slots in Anim Graph.
///
API_FUNCTION() void StopSlotAnimation();
///
/// Stops the animation playback on the slot in Anim Graph.
///
/// The name of the slot.
/// The animation to stop.
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim);
///
/// Pauses all the animations playback on the all slots in Anim Graph.
///
API_FUNCTION() void PauseSlotAnimation();
///
/// Pauses the animation playback on the slot in Anim Graph.
///
/// The name of the slot.
/// The animation to pause.
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim);
///
/// Checks if the any animation playback is active on the any slot in Anim Graph (not paused).
///
API_FUNCTION() bool IsPlayingSlotAnimation();
///
/// Checks if the animation playback is active on the slot in Anim Graph (not paused).
///
/// The name of the slot.
/// The animation to check.
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim);
private:
void ApplyRootMotion(const RootMotionData& rootMotionDelta);
void SyncParameters();
void Update();
void UpdateLocalBounds();
void UpdateBounds();
void UpdateSockets();
void OnAnimationUpdated_Async();
void OnAnimationUpdated_Sync();
void OnAnimationUpdated();
void OnSkinnedModelChanged();
void OnSkinnedModelLoaded();
void OnGraphChanged();
void OnGraphLoaded();
public:
// [ModelInstanceActor]
bool HasContentLoaded() const override;
void Draw(RenderContext& renderContext) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
BoundingBox GetEditorBox() const override;
#endif
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override;
protected:
// [ModelInstanceActor]
void BeginPlay(SceneBeginData* data) override;
void EndPlay() override;
void OnEnable() override;
void OnDisable() override;
void OnActiveInTreeChanged() override;
void OnTransformChanged() override;
};