// 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; };