├── Resources ├── new.png ├── icon128.pdn └── ButtonIcon_40x.png ├── CleanAll.bat ├── Source ├── Nobunanim │ ├── Private │ │ ├── ProceduralGaitInterface.cpp │ │ ├── ProceduralAnimAsset.cpp │ │ ├── GaitDataAsset.cpp │ │ ├── NAnim_AnimInstanceBase.cpp │ │ ├── NobunanimModule.cpp │ │ ├── NobunanimSettings.cpp │ │ ├── DebugPlayerController.cpp │ │ ├── Nobunanim.h │ │ ├── AnimNodes │ │ │ └── AnimNode_SafeCCDIK.cpp │ │ ├── ProceduralAnimator.cpp │ │ ├── ProceduralGaitControllerComponent.cpp │ │ └── ProceduralGaitAnimInstance.cpp │ ├── Public │ │ ├── NobunanimModule.h │ │ ├── DebugPlayerController.h │ │ ├── NAnim_AnimInstanceBase.h │ │ ├── GaitDataAsset.h │ │ ├── NobunanimSettings.h │ │ ├── AnimNodes │ │ │ └── AnimNode_SafeCCDIK.h │ │ ├── ProceduralGaitControllerComponent.h │ │ ├── ProceduralAnimator.h │ │ ├── ProceduralGaitAnimInstance.h │ │ ├── ProceduralGaitInterface.h │ │ └── ProceduralAnimAsset.h │ └── Nobunanim.Build.cs └── NobunanimEditor │ ├── Public │ ├── NobunanimEditorModule.h │ ├── SafeCCDIKEditMode.h │ ├── AnimGraphNode_SafeCCDIK.h │ └── NobunanimBaseEditMode.h │ ├── Private │ ├── NobunanimEditorModule.cpp │ ├── SafeCCDIKEditMode.cpp │ ├── NobunanimEditor.h │ ├── AnimGraphNode_SafeCCDIK.cpp │ └── NobunanimBaseEditMode.cpp │ └── NobunanimEditor.Build.cs ├── README.md ├── Nobunanim.uplugin ├── LICENSE └── .gitignore /Resources/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nobuna-no/Nobunanim/HEAD/Resources/new.png -------------------------------------------------------------------------------- /Resources/icon128.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nobuna-no/Nobunanim/HEAD/Resources/icon128.pdn -------------------------------------------------------------------------------- /Resources/ButtonIcon_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nobuna-no/Nobunanim/HEAD/Resources/ButtonIcon_40x.png -------------------------------------------------------------------------------- /CleanAll.bat: -------------------------------------------------------------------------------- 1 | :: Clean files 2 | RMDIR /s /q .vs 3 | RMDIR /s /q Binaries 4 | RMDIR /s /q Intermediate 5 | RMDIR /s /q Saved 6 | DEL /s /q *.sln 7 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/ProceduralGaitInterface.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralGaitInterface.h" 5 | 6 | // Add default functionality here for any IProceduralGaitInterface functions that are not pure virtual. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nobunanim 2 | Plugin for designing procedural animation on Unreal Engine 4. 3 | If there are things you can use, use it for free (under MIT license). 4 | 5 | ## ✦ Credits 6 | * http://runevision.com/thesis/rune_skovbo_johansen_thesis.pdf 7 | * http://docsdrive.com/pdfs/ansinet/ajsr/2015/165-181.pdf 8 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/NobunanimModule.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FNobunanimModule : public IModuleInterface 8 | { 9 | public: 10 | virtual void StartupModule() override; 11 | virtual void ShutdownModule() override; 12 | }; 13 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Public/NobunanimEditorModule.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FNobunanimEditorModule : public IModuleInterface 8 | { 9 | public: 10 | virtual void StartupModule() override; 11 | virtual void ShutdownModule() override; 12 | }; 13 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/ProceduralAnimAsset.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralAnimAsset.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | float UProceduralAnimAsset::GetFrameRatio() const 12 | { 13 | return (float)AnimationFrameRate / (float)AnimationFrameCount; 14 | } -------------------------------------------------------------------------------- /Source/Nobunanim/Private/GaitDataAsset.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "GaitDataAsset.h" 5 | 6 | #include "Nobunanim/Public/NobunanimSettings.h" 7 | 8 | 9 | /** Return the ratio according to the animation framecount. (1 sec = 60frames). */ 10 | float UGaitDataAsset::GetFrameRatio() const 11 | { 12 | return (float)UNobunanimSettings::GetFramePerSecond() / (float)AnimationFrameCount;// /*/ (float)TargetFramePerSecond*/; 13 | } -------------------------------------------------------------------------------- /Source/Nobunanim/Private/NAnim_AnimInstanceBase.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "NAnim_AnimInstanceBase.h" 5 | 6 | #include "Nobunanim/Public/ProceduralAnimator.h" 7 | 8 | 9 | void UNAnim_AnimInstanceBase::NativeInitializeAnimation() 10 | { 11 | Super::NativeInitializeAnimation(); 12 | 13 | animator = NewObject(); 14 | if (animator) 15 | { 16 | animator->Initialize(this->GetOwningComponent()); 17 | animator->SetActive(true); 18 | } 19 | } -------------------------------------------------------------------------------- /Source/Nobunanim/Public/DebugPlayerController.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/PlayerController.h" 7 | #include "DebugPlayerController.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class NOBUNANIM_API ADebugPlayerController : public APlayerController 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/NobunanimModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #include "NobunanimModule.h" 4 | #include "Nobunanim.h" 5 | 6 | DEFINE_LOG_CATEGORY(logNobunanim) 7 | 8 | #define LOCTEXT_NAMESPACE "FNobunanimModule" 9 | 10 | void FNobunanimModule::StartupModule() 11 | { 12 | } 13 | 14 | void FNobunanimModule::ShutdownModule() 15 | { 16 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 17 | // we call this function before unloading the module. 18 | } 19 | 20 | #undef LOCTEXT_NAMESPACE 21 | 22 | IMPLEMENT_MODULE(FNobunanimModule, Nobunanim) 23 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/NobunanimSettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #include "NobunanimSettings.h" 4 | 5 | /** Static accessor of FramePerSecond. */ 6 | int32 UNobunanimSettings::GetFramePerSecond() 7 | { 8 | return GetDefault()->FramePerSecond; 9 | } 10 | 11 | /** Gets the specified LOD setting. Return default one if invalid settings or @Lod. */ 12 | FProceduralGaitLODSettings UNobunanimSettings::GetLODSetting(int32 Lod) 13 | { 14 | const UNobunanimSettings* Default = GetDefault(); 15 | 16 | return Default->ProceduralGaitLODSettings.Contains(Lod) ? Default->ProceduralGaitLODSettings[Lod] : FProceduralGaitLODSettings(); 17 | } -------------------------------------------------------------------------------- /Source/Nobunanim/Public/NAnim_AnimInstanceBase.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include "NAnim_AnimInstanceBase.generated.h" 7 | 8 | /** 9 | * 10 | */ 11 | UCLASS() 12 | class NOBUNANIM_API UNAnim_AnimInstanceBase : public UAnimInstance 13 | { 14 | GENERATED_BODY() 15 | 16 | 17 | protected: 18 | /** PARAMETERS 19 | */ 20 | UPROPERTY(Category = "Nobunanim", BlueprintReadOnly) 21 | class UProceduralAnimator* animator; 22 | 23 | public: 24 | /** NATIVE ANIM INSTANCE OVERRIDE 25 | */ 26 | /** Native initialization override point. */ 27 | virtual void NativeInitializeAnimation() override; 28 | }; 29 | -------------------------------------------------------------------------------- /Nobunanim.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Nobunanimation", 6 | "Description": "Plugin for designing procedural animations.", 7 | "Category": "Animation", 8 | "CreatedBy": "Nobuna-no", 9 | "CreatedByURL": "https://github.com/Nobuna-no", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "EnabledByDefault": true, 14 | "CanContainContent": true, 15 | "IsBetaVersion": true, 16 | "Installed": false, 17 | 18 | "Modules": [ 19 | { 20 | "Name": "Nobunanim", 21 | "Type": "Runtime", 22 | "LoadingPhase": "Default" 23 | }, 24 | { 25 | "Name": "NobunanimEditor", 26 | "Type": "Editor", 27 | "LoadingPhase": "PostEngineInit" 28 | }, 29 | ] 30 | } -------------------------------------------------------------------------------- /Source/NobunanimEditor/Public/SafeCCDIKEditMode.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "NobunanimBaseEditMode.h" 6 | 7 | class FSafeCCDIKEditMode : public FNobunanimBaseEditMode 8 | { 9 | public: 10 | /** IAnimNodeEditMode interface */ 11 | virtual void EnterMode(class UAnimGraphNode_Base* InEditorNode, struct FAnimNode_Base* InRuntimeNode) override; 12 | virtual void ExitMode() override; 13 | virtual FVector GetWidgetLocation() const override; 14 | virtual FWidget::EWidgetMode GetWidgetMode() const override; 15 | virtual void DoTranslation(FVector& InTranslation) override; 16 | virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; 17 | private: 18 | struct FAnimNode_SafeCCDIK* RuntimeNode; 19 | class UAnimGraphNode_SafeCCDIK* GraphNode; 20 | }; -------------------------------------------------------------------------------- /Source/Nobunanim/Private/DebugPlayerController.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "DebugPlayerController.h" 4 | 5 | #include "GameFramework/Pawn.h" 6 | 7 | #include "ProceduralGaitControllerComponent.h" 8 | 9 | 10 | bool ADebugPlayerController::ProcessConsoleExec(const TCHAR* _Cmd, FOutputDevice& _Ar, UObject* _Executor) 11 | { 12 | bool handled = Super::ProcessConsoleExec(_Cmd, _Ar, _Executor); 13 | 14 | if (!handled) 15 | { 16 | TArray ChildrenTmp; 17 | GetPawn()->GetComponents(ChildrenTmp); 18 | if (ChildrenTmp.Num()) 19 | { 20 | UProceduralGaitControllerComponent* GaitComponent = ChildrenTmp[0]; 21 | handled &= GaitComponent->ProcessConsoleExec(_Cmd, _Ar, _Executor); 22 | } 23 | } 24 | 25 | return handled; 26 | } 27 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Private/NobunanimEditorModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #include "NobunanimEditorModule.h" 4 | #include "NobunanimEditor.h" 5 | #include "Textures/SlateIcon.h" 6 | #include "SafeCCDIKEditMode.h" 7 | 8 | DEFINE_LOG_CATEGORY(logNobunanimEditor) 9 | 10 | #define LOCTEXT_NAMESPACE "FNobunanimEditorModule" 11 | 12 | void FNobunanimEditorModule::StartupModule() 13 | { 14 | FEditorModeRegistry::Get().RegisterMode("AnimGraph.SkeletalControl.SafeCCDIK", LOCTEXT("FSafeCCDIKEditMode", "Safe CCDIK"), FSlateIcon(), false); 15 | } 16 | 17 | void FNobunanimEditorModule::ShutdownModule() 18 | { 19 | FEditorModeRegistry::Get().UnregisterMode("AnimGraph.SkeletalControl.SafeCCDIK"); 20 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 21 | // we call this function before unloading the module. 22 | } 23 | 24 | #undef LOCTEXT_NAMESPACE 25 | 26 | IMPLEMENT_MODULE(FNobunanimEditorModule, NobunanimEditor) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Morgan Hoarau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.ipa 36 | 37 | # These project files can be generated by the engine 38 | *.xcodeproj 39 | *.xcworkspace 40 | *.sln 41 | *.suo 42 | *.opensdf 43 | *.sdf 44 | *.VC.db 45 | *.VC.opendb 46 | 47 | # Precompiled Assets 48 | SourceArt/**/*.png 49 | SourceArt/**/*.tga 50 | 51 | # Binary Files 52 | Binaries/* 53 | 54 | # Builds 55 | Build/* 56 | 57 | # Whitelist PakBlacklist-.txt files 58 | !Build/*/ 59 | Build/*/** 60 | !Build/*/PakBlacklist*.txt 61 | 62 | # Don't ignore icon files in Build 63 | !Build/**/*.ico 64 | 65 | # Built data for maps 66 | *_BuiltData.uasset 67 | 68 | # Configuration files generated by the Editor 69 | Saved/* 70 | 71 | # Compiled source files for the engine to use 72 | Intermediate/* 73 | 74 | # Cache files for the editor to use 75 | DerivedDataCache/* 76 | -------------------------------------------------------------------------------- /Source/Nobunanim/Nobunanim.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class Nobunanim : ModuleRules 6 | { 7 | public Nobunanim(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | PrivateIncludePaths.AddRange( 18 | new string[] { 19 | // ... add other private include paths required here ... 20 | } 21 | ); 22 | 23 | 24 | PublicDependencyModuleNames.AddRange( 25 | new string[] 26 | { 27 | "Core", 28 | "AnimGraphRuntime", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | // ... add private dependencies that you statically link with here ... 40 | } 41 | ); 42 | 43 | 44 | DynamicallyLoadedModuleNames.AddRange( 45 | new string[] 46 | { 47 | // ... add any modules that your module loads dynamically here ... 48 | } 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Public/AnimGraphNode_SafeCCDIK.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "EdGraph/EdGraphNodeUtils.h" 6 | #include "AnimGraphNode_SkeletalControlBase.h" 7 | 8 | #include "Nobunanim/Public/AnimNodes/AnimNode_SafeCCDIK.h" 9 | 10 | #include "AnimGraphNode_SafeCCDIK.generated.h" 11 | 12 | // Editor node for CCDIK IK skeletal controller 13 | UCLASS(MinimalAPI, Experimental) 14 | class UAnimGraphNode_SafeCCDIK : public UAnimGraphNode_SkeletalControlBase 15 | { 16 | GENERATED_UCLASS_BODY() 17 | 18 | UPROPERTY(EditAnywhere, Category = Settings) 19 | FAnimNode_SafeCCDIK Node; 20 | 21 | public: 22 | // UEdGraphNode interface 23 | virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; 24 | // End of UEdGraphNode interface 25 | 26 | // UAnimGraphNode_Base interface 27 | virtual void CopyNodeDataToPreviewNode(FAnimNode_Base* AnimNode) override; 28 | virtual FEditorModeID GetEditorMode() const override; 29 | // End of UAnimGraphNode_Base interface 30 | 31 | // UObject interface 32 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 33 | 34 | protected: 35 | // UAnimGraphNode_SkeletalControlBase interface 36 | virtual FText GetControllerDescription() const override; 37 | virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; } 38 | // End of UAnimGraphNode_SkeletalControlBase interface 39 | 40 | private: 41 | /** Constructing FText strings can be costly, so we cache the node's title */ 42 | FNodeTitleTextTable CachedNodeTitles; 43 | }; -------------------------------------------------------------------------------- /Source/NobunanimEditor/NobunanimEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class NobunanimEditor : ModuleRules 6 | { 7 | public NobunanimEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | PrivateIncludePaths.AddRange( 18 | new string[] { 19 | // ... add other private include paths required here ... 20 | } 21 | ); 22 | 23 | 24 | PublicDependencyModuleNames.AddRange(new string[] 25 | { 26 | "AnimGraph", 27 | }); 28 | 29 | PrivateDependencyModuleNames.AddRange(new string[] 30 | { 31 | "Core", 32 | "CoreUObject", 33 | "Engine", 34 | "InputCore", 35 | "AnimGraph", 36 | "BlueprintGraph", 37 | "Persona", 38 | "UnrealEd", 39 | "AnimGraph", 40 | "AnimGraphRuntime", 41 | "SlateCore", 42 | "Nobunanim" 43 | }); 44 | 45 | // PrivateDependencyModuleNames.AddRange( 46 | //new string[] 47 | // { 48 | // "Projects", 49 | // "InputCore", 50 | // "UnrealEd", 51 | // "LevelEditor", 52 | // "CoreUObject", 53 | // "Engine", 54 | // "Slate", 55 | // "SlateCore", 56 | // "ImageWrapper", 57 | // "AssetRegistry", 58 | // "PropertyEditor", 59 | // "AdvancedPreviewScene", 60 | 61 | // // ... add private dependencies that you statically link with here ... 62 | //} 63 | //); 64 | 65 | 66 | DynamicallyLoadedModuleNames.AddRange( 67 | new string[] 68 | { 69 | // ... add any modules that your module loads dynamically here ... 70 | } 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/GaitDataAsset.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "Nobunanim/Public/ProceduralGaitInterface.h" 9 | 10 | #include "GaitDataAsset.generated.h" 11 | 12 | 13 | /** 14 | */ 15 | USTRUCT(BlueprintType) 16 | struct FGaitGroundReflectionPlaneData 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | /** First effector use to get the of the ground level. */ 22 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 23 | FName EffectorName1; 24 | 25 | /** Second effector use to get the of the ground level. */ 26 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 27 | FName EffectorName2; 28 | 29 | /** Third effector use to get the of the ground level. */ 30 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 31 | FName EffectorName3; 32 | 33 | /** Third effector use to get the of the ground level. */ 34 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 35 | FColor PlaneDebugColor = FColor::Red; 36 | }; 37 | 38 | /** 39 | */ 40 | USTRUCT(BlueprintType) 41 | struct FGaitGroundReflectionData 42 | { 43 | GENERATED_BODY() 44 | 45 | /** Array of planes defining the ground reflection. */ 46 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Debug", EditAnywhere, BlueprintReadWrite) 47 | TArray Planes;}; 48 | 49 | 50 | /** 51 | * 52 | */ 53 | UCLASS() 54 | class NOBUNANIM_API UGaitDataAsset : public UPrimaryDataAsset 55 | { 56 | GENERATED_BODY() 57 | 58 | public: 59 | /** The key value is the name of the effector or bone that should apply the gait data. */ 60 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data", EditAnywhere, BlueprintReadOnly) 61 | TMap GaitSwingValues; 62 | 63 | ///** @to do: documentation. */ 64 | //UPROPERTY(Category = "[NOBUNANIM]|Gait Data", EditAnywhere, BlueprintReadOnly) 65 | //TMap GroundReflectionPerEffector; 66 | 67 | /** Animation frame number. 60 frame = 1 second. */ 68 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data", EditAnywhere, BlueprintReadOnly) 69 | int AnimationFrameCount = 60; 70 | 71 | /** Should this gait be played only if the velocity isn't zero. */ 72 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data", EditAnywhere, BlueprintReadOnly) 73 | bool bComputeWithVelocityOnly = true; 74 | 75 | public: 76 | /** Return the ratio according to the animation framecount. (1 sec = 60frames). */ 77 | UFUNCTION(Category = "[NOBUNANIM]|Gait Data", BlueprintCallable) 78 | float GetFrameRatio() const; 79 | }; 80 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Private/SafeCCDIKEditMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "SafeCCDIKEditMode.h" 4 | 5 | #include "AnimGraphNode_SafeCCDIK.h" 6 | #include "IPersonaPreviewScene.h" 7 | #include "Animation/DebugSkelMeshComponent.h" 8 | 9 | 10 | void FSafeCCDIKEditMode::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimNode_Base* InRuntimeNode) 11 | { 12 | RuntimeNode = static_cast(InRuntimeNode); 13 | GraphNode = CastChecked(InEditorNode); 14 | 15 | FNobunanimBaseEditMode::EnterMode(InEditorNode, InRuntimeNode); 16 | } 17 | 18 | void FSafeCCDIKEditMode::ExitMode() 19 | { 20 | RuntimeNode = nullptr; 21 | GraphNode = nullptr; 22 | 23 | FNobunanimBaseEditMode::ExitMode(); 24 | } 25 | 26 | FVector FSafeCCDIKEditMode::GetWidgetLocation() const 27 | { 28 | EBoneControlSpace Space = RuntimeNode->EffectorLocationSpace; 29 | FVector Location = RuntimeNode->EffectorLocation; 30 | FBoneSocketTarget Target = RuntimeNode->EffectorTarget; 31 | 32 | USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); 33 | return ConvertWidgetLocation(SkelComp, RuntimeNode->ForwardedPose, Target, Location, Space); 34 | } 35 | 36 | FWidget::EWidgetMode FSafeCCDIKEditMode::GetWidgetMode() const 37 | { 38 | USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); 39 | int32 TipBoneIndex = SkelComp->GetBoneIndex(RuntimeNode->TipBone.BoneName); 40 | int32 RootBoneIndex = SkelComp->GetBoneIndex(RuntimeNode->RootBone.BoneName); 41 | 42 | if (TipBoneIndex != INDEX_NONE && RootBoneIndex != INDEX_NONE) 43 | { 44 | return FWidget::WM_Translate; 45 | } 46 | 47 | return FWidget::WM_None; 48 | } 49 | 50 | void FSafeCCDIKEditMode::DoTranslation(FVector& InTranslation) 51 | { 52 | USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); 53 | 54 | FVector Offset = ConvertCSVectorToBoneSpace(SkelComp, InTranslation, RuntimeNode->ForwardedPose, RuntimeNode->EffectorTarget, GraphNode->Node.EffectorLocationSpace); 55 | 56 | RuntimeNode->EffectorLocation += Offset; 57 | GraphNode->Node.EffectorLocation = RuntimeNode->EffectorLocation; 58 | GraphNode->SetDefaultValue(GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_SafeCCDIK, EffectorLocation), RuntimeNode->EffectorLocation); 59 | } 60 | 61 | void FSafeCCDIKEditMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) 62 | { 63 | #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) 64 | // draw line from root bone to tip bone if available 65 | if (RuntimeNode && RuntimeNode->DebugLines.Num() > 0) 66 | { 67 | USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); 68 | FTransform CompToWorld = SkelComp->GetComponentToWorld(); 69 | 70 | PDI->DrawPoint(CompToWorld.TransformPosition(RuntimeNode->DebugLines[0]), FLinearColor::Red, 5, SDPG_Foreground); 71 | 72 | // no component space 73 | for (int32 Index = 1; Index < RuntimeNode->DebugLines.Num(); ++Index) 74 | { 75 | FVector Start = CompToWorld.TransformPosition(RuntimeNode->DebugLines[Index - 1]); 76 | FVector End = CompToWorld.TransformPosition(RuntimeNode->DebugLines[Index]); 77 | 78 | PDI->DrawLine(Start, End, FLinearColor::Red, SDPG_Foreground); 79 | PDI->DrawPoint(End, FLinearColor::White, 5, SDPG_Foreground); 80 | } 81 | 82 | } 83 | #endif // #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) 84 | } -------------------------------------------------------------------------------- /Source/NobunanimEditor/Private/NobunanimEditor.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(logNobunanimEditor, Log, All); 8 | 9 | 10 | /** Time when this is called. */ 11 | #define DEBUG_TIME " : " + FString(__TIME__) + " : " 12 | /** Date when this is called. */ 13 | #define DEBUG_DATE "[" + FString(__DATE__) + "]" 14 | /** Folder name where this is called. */ 15 | #define DEBUG_TRACE_FOLDER (FString(__FILE__)) 16 | /** Current Line number in the code where this is called. */ 17 | #define DEBUG_TRACE_LINE FString::FromInt(__LINE__) 18 | /** Class name + Function name where this is called. */ 19 | #define DEBUG_TRACE_CLASS_FUNCTION (FString(__FUNCTION__)) 20 | /** Class name where this is called. */ 21 | #define DEBUG_TRACE_CLASS (FString(__FUNCTION__).Left(FString(__FUNCTION__).Find(TEXT(":"))) 22 | /** Function name where this is called. */ 23 | #define DEBUG_TRACE_FUNCTION (FString(__FUNCTION__).Right(FString(__FUNCTION__).Len() - FString(__FUNCTION__).Find(TEXT("::")) - 2 )) 24 | /** Function signature where this is called. */ 25 | #define DEBUG_TRACE_FUNCTION_SIGNATURE (FString(_FUNSIG_)) 26 | 27 | #define DEBUG_SEMI_TRACE_CALL FString(DEBUG_TRACE_CLASS_FUNCTION + FString("(") + DEBUG_TRACE_LINE + FString(")")) 28 | #define DEBUG_TRACE_CALL FString(FString("(") +DEBUG_TRACE_FOLDER + FString("):") + DEBUG_TRACE_CLASS_FUNCTION + FString("> at Line ") + DEBUG_TRACE_LINE) 29 | 30 | /** Detailed debug screen message. 31 | * MESSAGE a simple string. 32 | * TIME_TO_DISPLAY time to display on screen. 33 | * COLOR see @FColor::... */ 34 | #define DEBUG_SCREEN_LOG(MESSAGE, TIME_TO_DISPLAY, COLOR)\ 35 | GEngine->AddOnScreenDebugMessage(-1, TIME_TO_DISPLAY, COLOR, *(DEBUG_SEMI_TRACE_CALL + ": " + MESSAGE)); 36 | /** Detailed debug screen message for formatted string. 37 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). 38 | * TIME_TO_DISPLAY time to display on screen. 39 | * COLOR see @FColor::... */ 40 | #define DEBUG_SCREEN_LOG_FORMAT(FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR,...)\ 41 | GEngine->AddOnScreenDebugMessage(-1, TIME_TO_DISPLAY, COLOR, *(DEBUG_SEMI_TRACE_CALL + FString(": ") + (FString::Printf(TEXT(FORMAT_MESSAGE), ##__VA_ARGS__)))) ; 42 | 43 | /** Detailed debug log message. 44 | * LOG_LEVEL (Log, Warning, Error, Fatal). 45 | * MESSAGE a simple string.*/ 46 | #define DEBUG_LOG(LOG_LEVEL, MESSAGE)\ 47 | UE_LOG(logNobunanimEditor, LOG_LEVEL, TEXT("%s:\n\t|=> %s"), *DEBUG_TRACE_CALL, *FString(MESSAGE)) 48 | /** Detailed debug log message for formatted string. 49 | * LOG_LEVEL (Log, Warning, Error, Fatal). 50 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). */ 51 | #define DEBUG_LOG_FORMAT(LOG_LEVEL, FORMAT_MESSAGE, ...)\ 52 | UE_LOG(logNobunanimEditor, LOG_LEVEL, TEXT("%s:\n\t|=> %s"), *DEBUG_TRACE_CALL, *FString::Printf(TEXT(FORMAT_MESSAGE), ##__VA_ARGS__)) 53 | 54 | 55 | /** Detailed debug screen + log message. 56 | * LOG_LEVEL (Log, Warning, Error, Fatal). 57 | * MESSAGE a simple string. 58 | * TIME_TO_DISPLAY time to display on screen. 59 | * COLOR see @FColor::... */ 60 | #define DEBUG_LOG_ALL(LOG_LEVEL, MESSAGE, TIME_TO_DISPLAY, COLOR)\ 61 | DEBUG_LOG(LOG_LEVEL, MESSAGE)\ 62 | DEBUG_SCREEN_LOG(MESSAGE, TIME_TO_DISPLAY, COLOR) 63 | 64 | /** Detailed debug screen + log message for formatted string. 65 | * LOG_LEVEL (Log, Warning, Error, Fatal). 66 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). 67 | * TIME_TO_DISPLAY time to display on screen. 68 | * COLOR see @FColor::... */ 69 | #define DEBUG_LOG_ALL_FORMAT(LOG_LEVEL, FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR, ...)\ 70 | DEBUG_LOG_FORMAT(LOG_LEVEL, FORMAT_MESSAGE,##__VA_ARGS__)\ 71 | DEBUG_SCREEN_LOG_FORMAT(FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR,##__VA_ARGS__) 72 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Private/AnimGraphNode_SafeCCDIK.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "AnimGraphNode_SafeCCDIK.h" 4 | #include "Animation/AnimInstance.h" 5 | #include "AnimNodeEditModes.h" 6 | 7 | ///////////////////////////////////////////////////// 8 | // UAnimGraphNode_SafeCCDIK 9 | 10 | #define LOCTEXT_NAMESPACE "A3Nodes" 11 | 12 | UAnimGraphNode_SafeCCDIK::UAnimGraphNode_SafeCCDIK(const FObjectInitializer& ObjectInitializer) 13 | : Super(ObjectInitializer) 14 | { 15 | } 16 | 17 | FText UAnimGraphNode_SafeCCDIK::GetControllerDescription() const 18 | { 19 | return LOCTEXT("SafeCCDIK", "SafeCCDIK"); 20 | } 21 | 22 | FText UAnimGraphNode_SafeCCDIK::GetNodeTitle(ENodeTitleType::Type TitleType) const 23 | { 24 | if ((TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)) 25 | { 26 | return GetControllerDescription(); 27 | } 28 | // @TODO: the bone can be altered in the property editor, so we have to 29 | // choose to mark this dirty when that happens for this to properly work 30 | else //if (!CachedNodeTitles.IsTitleCached(TitleType, this)) 31 | { 32 | FFormatNamedArguments Args; 33 | Args.Add(TEXT("ControllerDescription"), GetControllerDescription()); 34 | Args.Add(TEXT("RootBoneName"), FText::FromName(Node.RootBone.BoneName)); 35 | Args.Add(TEXT("TipBoneName"), FText::FromName(Node.TipBone.BoneName)); 36 | 37 | // FText::Format() is slow, so we cache this to save on performance 38 | if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) 39 | { 40 | CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_SafeCCDIKBone_ListTitle", "{ControllerDescription} - Root: {RootBoneName}, Tip: {TipBoneName} "), Args), this); 41 | } 42 | else 43 | { 44 | CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_SafeCCDIKBone_Title", "{ControllerDescription}\nRoot: {RootBoneName} - Tip: {TipBoneName} "), Args), this); 45 | } 46 | } 47 | return CachedNodeTitles[TitleType]; 48 | } 49 | 50 | void UAnimGraphNode_SafeCCDIK::CopyNodeDataToPreviewNode(FAnimNode_Base* InPreviewNode) 51 | { 52 | FAnimNode_SafeCCDIK* CCDIK = static_cast(InPreviewNode); 53 | 54 | // copies Pin values from the internal node to get data which are not compiled yet 55 | CCDIK->EffectorLocation = Node.EffectorLocation; 56 | } 57 | 58 | FEditorModeID UAnimGraphNode_SafeCCDIK::GetEditorMode() const 59 | { 60 | return "AnimGraph.SkeletalControl.SafeCCDIK"; 61 | } 62 | 63 | void UAnimGraphNode_SafeCCDIK::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) 64 | { 65 | Super::PostEditChangeProperty(PropertyChangedEvent); 66 | 67 | FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; 68 | if (PropertyName == GET_MEMBER_NAME_CHECKED(FBoneReference, BoneName)) 69 | { 70 | USkeleton* Skeleton = GetAnimBlueprint()->TargetSkeleton; 71 | if (Node.TipBone.BoneName != NAME_None && Node.RootBone.BoneName != NAME_None) 72 | { 73 | const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); 74 | const int32 TipBoneIndex = RefSkeleton.FindBoneIndex(Node.TipBone.BoneName); 75 | const int32 RootBoneIndex = RefSkeleton.FindBoneIndex(Node.RootBone.BoneName); 76 | 77 | if (TipBoneIndex != INDEX_NONE && RootBoneIndex != INDEX_NONE) 78 | { 79 | const int32 Depth = RefSkeleton.GetDepthBetweenBones(TipBoneIndex, RootBoneIndex); 80 | if (Depth >= 0) 81 | { 82 | Node.ResizeRotationLimitPerJoints(Depth + 1); 83 | } 84 | else 85 | { 86 | Node.ResizeRotationLimitPerJoints(0); 87 | } 88 | } 89 | else 90 | { 91 | Node.ResizeRotationLimitPerJoints(0); 92 | } 93 | } 94 | else 95 | { 96 | Node.ResizeRotationLimitPerJoints(0); 97 | } 98 | } 99 | } 100 | 101 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/Nobunanim/Private/Nobunanim.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(logNobunanim, Log, All); 8 | 9 | DECLARE_STATS_GROUP(TEXT("Nobunanim"), STATGROUP_Nobunanim, STATCAT_Advanced); 10 | 11 | #define NOBUNANIM_SCOPE_COUNTER(UniqueFunctionName) DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Nobunanim - " #UniqueFunctionName), STAT_##UniqueFunctionName, STATGROUP_Nobunanim); 12 | 13 | 14 | /** Time when this is called. */ 15 | #define DEBUG_TIME " : " + FString(__TIME__) + " : " 16 | /** Date when this is called. */ 17 | #define DEBUG_DATE "[" + FString(__DATE__) + "]" 18 | /** Folder name where this is called. */ 19 | #define DEBUG_TRACE_FOLDER (FString(__FILE__)) 20 | /** Current Line number in the code where this is called. */ 21 | #define DEBUG_TRACE_LINE FString::FromInt(__LINE__) 22 | /** Class name + Function name where this is called. */ 23 | #define DEBUG_TRACE_CLASS_FUNCTION (FString(__FUNCTION__)) 24 | /** Class name where this is called. */ 25 | #define DEBUG_TRACE_CLASS (FString(__FUNCTION__).Left(FString(__FUNCTION__).Find(TEXT(":"))) 26 | /** Function name where this is called. */ 27 | #define DEBUG_TRACE_FUNCTION (FString(__FUNCTION__).Right(FString(__FUNCTION__).Len() - FString(__FUNCTION__).Find(TEXT("::")) - 2 )) 28 | /** Function signature where this is called. */ 29 | #define DEBUG_TRACE_FUNCTION_SIGNATURE (FString(_FUNSIG_)) 30 | 31 | #define DEBUG_SEMI_TRACE_CALL FString(DEBUG_TRACE_CLASS_FUNCTION + FString("(") + DEBUG_TRACE_LINE + FString(")")) 32 | #define DEBUG_TRACE_CALL FString(FString("(") +DEBUG_TRACE_FOLDER + FString("):") + DEBUG_TRACE_CLASS_FUNCTION + FString("> at Line ") + DEBUG_TRACE_LINE) 33 | 34 | /** Detailed debug screen message. 35 | * MESSAGE a simple string. 36 | * TIME_TO_DISPLAY time to display on screen. 37 | * COLOR see @FColor::... */ 38 | #define DEBUG_SCREEN_LOG(MESSAGE, TIME_TO_DISPLAY, COLOR)\ 39 | GEngine->AddOnScreenDebugMessage(-1, TIME_TO_DISPLAY, COLOR, *(DEBUG_SEMI_TRACE_CALL + ": " + MESSAGE)); 40 | /** Detailed debug screen message for formatted string. 41 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). 42 | * TIME_TO_DISPLAY time to display on screen. 43 | * COLOR see @FColor::... */ 44 | #define DEBUG_SCREEN_LOG_FORMAT(FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR,...)\ 45 | GEngine->AddOnScreenDebugMessage(-1, TIME_TO_DISPLAY, COLOR, *(DEBUG_SEMI_TRACE_CALL + FString(": ") + (FString::Printf(TEXT(FORMAT_MESSAGE), ##__VA_ARGS__)))) ; 46 | 47 | /** Detailed debug log message. 48 | * LOG_LEVEL (Log, Warning, Error, Fatal). 49 | * MESSAGE a simple string.*/ 50 | #define DEBUG_LOG(LOG_LEVEL, MESSAGE)\ 51 | UE_LOG(logNobunanim, LOG_LEVEL, TEXT("%s:\n\t|=> %s"), *DEBUG_TRACE_CALL, *FString(MESSAGE)) 52 | /** Detailed debug log message for formatted string. 53 | * LOG_LEVEL (Log, Warning, Error, Fatal). 54 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). */ 55 | #define DEBUG_LOG_FORMAT(LOG_LEVEL, FORMAT_MESSAGE, ...)\ 56 | UE_LOG(logNobunanim, LOG_LEVEL, TEXT("%s:\n\t|=> %s"), *DEBUG_TRACE_CALL, *FString::Printf(TEXT(FORMAT_MESSAGE), ##__VA_ARGS__)) 57 | 58 | 59 | /** Detailed debug screen + log message. 60 | * LOG_LEVEL (Log, Warning, Error, Fatal). 61 | * MESSAGE a simple string. 62 | * TIME_TO_DISPLAY time to display on screen. 63 | * COLOR see @FColor::... */ 64 | #define DEBUG_LOG_ALL(LOG_LEVEL, MESSAGE, TIME_TO_DISPLAY, COLOR)\ 65 | DEBUG_LOG(LOG_LEVEL, MESSAGE)\ 66 | DEBUG_SCREEN_LOG(MESSAGE, TIME_TO_DISPLAY, COLOR) 67 | 68 | /** Detailed debug screen + log message for formatted string. 69 | * LOG_LEVEL (Log, Warning, Error, Fatal). 70 | * FORMAT_MESSAGE is a formatted string (with %s, %d, %f, ...). 71 | * TIME_TO_DISPLAY time to display on screen. 72 | * COLOR see @FColor::... */ 73 | #define DEBUG_LOG_ALL_FORMAT(LOG_LEVEL, FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR, ...)\ 74 | DEBUG_LOG_FORMAT(LOG_LEVEL, FORMAT_MESSAGE,##__VA_ARGS__)\ 75 | DEBUG_SCREEN_LOG_FORMAT(FORMAT_MESSAGE, TIME_TO_DISPLAY, COLOR,##__VA_ARGS__) 76 | 77 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/NobunanimSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include "NobunanimSettings.generated.h" 7 | 8 | USTRUCT(BlueprintType) 9 | struct NOBUNANIM_API FProceduralGaitLODSettingsDebugData 10 | { 11 | GENERATED_BODY() 12 | 13 | /** Should debug LOD be displayed. */ 14 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD|Debug", EditAnywhere, Config) 15 | bool bShowLOD = false; 16 | 17 | /** Color of LOD. Also use for sphere trace IK. */ 18 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD|Debug", EditAnywhere, Config) 19 | FColor LODColor = FColor::White; 20 | 21 | /** Should debug IKs (traces) be displayed. */ 22 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD|Debug", EditAnywhere, Config) 23 | bool bShowCollisionCorrection = true; 24 | 25 | /** IK trace color. */ 26 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD|Debug", EditAnywhere, Config) 27 | FColor IKTraceColor = FColor::Red; 28 | 29 | /** Duration of traces.*/ 30 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD|Debug", EditAnywhere, Config) 31 | float IKTraceDuration = 0.f; 32 | }; 33 | 34 | /** Correction level. */ 35 | UENUM() 36 | enum class ENobunanimIKCorrectionLevel : uint8 37 | { 38 | IKL_Level0 UMETA(DisplayName = "None"), 39 | IKL_Level1 UMETA(DisplayName = "Raycast only"), 40 | IKL_Level2 UMETA(DisplayName = "Raycast & Spherecast"), 41 | }; 42 | 43 | USTRUCT(BlueprintType) 44 | struct NOBUNANIM_API FProceduralGaitLODSettings 45 | { 46 | GENERATED_BODY() 47 | 48 | /** Target frame per second (refresh rate) of the animation for this LOD. */ 49 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config, meta = (ClampMn = "0")) 50 | int32 TargetFPS = 60; 51 | 52 | /** May the delta time be computed as if the game was always running at TargetFPS?. */ 53 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 54 | bool bForceDeltaTimeAtTargetFPS = false; 55 | 56 | /** If required, may this LOD be allow to compute collision correction? 57 | * Can be used to optimized high LOD (far away) by disabling the collision computation. */ 58 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 59 | bool bCanComputeCollisionCorrection = true; 60 | 61 | /** May the trace be computed with complexes? Disable to gain performance. */ 62 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 63 | bool bTraceOnComplex = true; 64 | 65 | /** Effector correction IK.*/ 66 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 67 | ENobunanimIKCorrectionLevel CorrectionLevel = ENobunanimIKCorrectionLevel::IKL_Level1; 68 | 69 | #if WITH_EDITORONLY_DATA 70 | /** Debug Data. */ 71 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 72 | FProceduralGaitLODSettingsDebugData Debug; 73 | #endif 74 | }; 75 | 76 | UCLASS(Category = "[NOBUNANIM]|Settings", Config = Game, defaultConfig) 77 | class NOBUNANIM_API UNobunanimSettings : public UDeveloperSettings 78 | { 79 | GENERATED_BODY() 80 | 81 | protected: 82 | /** 83 | * Number of frame for 1 second. 84 | * Ratio to convert seconds in frames. 85 | * All the procedural gait system depends on this ratio (refresh rate, animation speed...). 86 | */ 87 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait", EditAnywhere, Config, meta = (ClampMn = "0")) 88 | int32 FramePerSecond = 60; 89 | 90 | /** Map of procedural gait settings. */ 91 | UPROPERTY(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", EditAnywhere, Config) 92 | TMap ProceduralGaitLODSettings; 93 | 94 | public: 95 | /** Static accessor of FramePerSecond. */ 96 | UFUNCTION(Category = "[NOBUNANIM]|Settings|Procedural Gait|LOD", BlueprintPure) 97 | static int32 GetFramePerSecond(); 98 | 99 | /** Gets the specified LOD setting. Return default one if invalid settings or @Lod. */ 100 | UFUNCTION(Category = "STARK|Settings|Matter", BlueprintPure) 101 | static FProceduralGaitLODSettings GetLODSetting(int32 Lod); 102 | }; -------------------------------------------------------------------------------- /Source/NobunanimEditor/Public/NobunanimBaseEditMode.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InputCoreTypes.h" 7 | #include "UnrealWidget.h" 8 | #include "IAnimNodeEditMode.h" 9 | #include "BonePose.h" 10 | 11 | class FCanvas; 12 | class FEditorViewportClient; 13 | class FPrimitiveDrawInterface; 14 | class USkeletalMeshComponent; 15 | struct FViewportClick; 16 | struct FBoneSocketTarget; 17 | 18 | /** Base implementation for anim node edit modes */ 19 | class FNobunanimBaseEditMode : public IAnimNodeEditMode 20 | { 21 | public: 22 | FNobunanimBaseEditMode(); 23 | 24 | /** IAnimNodeEditMode interface */ 25 | virtual ECoordSystem GetWidgetCoordinateSystem() const override; 26 | virtual FWidget::EWidgetMode GetWidgetMode() const override; 27 | virtual FWidget::EWidgetMode ChangeToNextWidgetMode(FWidget::EWidgetMode CurWidgetMode) override; 28 | virtual bool SetWidgetMode(FWidget::EWidgetMode InWidgetMode) override; 29 | virtual FName GetSelectedBone() const override; 30 | virtual void DoTranslation(FVector& InTranslation) override; 31 | virtual void DoRotation(FRotator& InRotation) override; 32 | virtual void DoScale(FVector& InScale) override; 33 | virtual void EnterMode(class UAnimGraphNode_Base* InEditorNode, struct FAnimNode_Base* InRuntimeNode) override; 34 | virtual void ExitMode() override; 35 | 36 | /** IPersonaEditMode interface */ 37 | virtual bool GetCameraTarget(FSphere& OutTarget) const override; 38 | virtual class IPersonaPreviewScene& GetAnimPreviewScene() const override; 39 | virtual void GetOnScreenDebugInfo(TArray& OutDebugInfo) const override; 40 | 41 | /** FEdMode interface */ 42 | virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; 43 | virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; 44 | virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override; 45 | virtual FVector GetWidgetLocation() const override; 46 | virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; 47 | virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; 48 | virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override; 49 | virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override; 50 | virtual bool GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) override; 51 | virtual bool GetCustomInputCoordinateSystem(FMatrix& InMatrix, void* InData) override; 52 | virtual bool ShouldDrawWidget() const override; 53 | virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; 54 | 55 | protected: 56 | // local conversion functions for drawing 57 | static void ConvertToComponentSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InTransform, FTransform & OutCSTransform, int32 BoneIndex, EBoneControlSpace Space); 58 | static void ConvertToBoneSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InCSTransform, FTransform & OutBSTransform, int32 BoneIndex, EBoneControlSpace Space); 59 | // convert drag vector in component space to bone space 60 | static FVector ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space); 61 | static FVector ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FBoneSocketTarget& InTarget, const EBoneControlSpace Space); 62 | // convert rotator in component space to bone space 63 | static FQuat ConvertCSRotationToBoneSpace(const USkeletalMeshComponent* SkelComp, FRotator& InCSRotator, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space); 64 | // convert widget location according to bone control space 65 | static FVector ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FName& BoneName, const FVector& InLocation, const EBoneControlSpace Space); 66 | static FVector ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FBoneSocketTarget& Target, const FVector& InLocation, const EBoneControlSpace Space); 67 | 68 | protected: 69 | /** The node we are operating on */ 70 | class UAnimGraphNode_Base* AnimNode; 71 | 72 | /** The runtime node in the preview scene */ 73 | struct FAnimNode_Base* RuntimeAnimNode; 74 | 75 | private: 76 | bool bManipulating; 77 | 78 | bool bInTransaction; 79 | }; 80 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/AnimNodes/AnimNode_SafeCCDIK.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "BoneControllers/AnimNode_SkeletalControlBase.h" 6 | #include "AnimNode_SafeCCDIK.generated.h" 7 | 8 | /** 9 | * Controller which implements the CCDIK IK approximation algorithm 10 | */ 11 | 12 | /** Transient structure for CCDIK node evaluation */ 13 | struct SafeCCDIKChainLink 14 | { 15 | public: 16 | /** Transform of bone in component space. */ 17 | FTransform Transform; 18 | 19 | /** Transform of bone in local space. This is mutable as their component space changes or parents*/ 20 | FTransform LocalTransform; 21 | 22 | /** Bone Index in SkeletalMesh */ 23 | FCompactPoseBoneIndex BoneIndex; 24 | 25 | /** Transform Index that this control will output */ 26 | int32 TransformIndex; 27 | 28 | /** Child bones which are overlapping this bone. 29 | * They have a zero length distance, so they will inherit this bone's transformation. */ 30 | TArray ChildZeroLengthTransformIndices; 31 | 32 | float CurrentAngleDelta; 33 | 34 | SafeCCDIKChainLink() 35 | : BoneIndex(INDEX_NONE) 36 | , TransformIndex(INDEX_NONE) 37 | , CurrentAngleDelta(0.f) 38 | { 39 | } 40 | 41 | SafeCCDIKChainLink(const FTransform& InTransform, const FTransform& InLocalTransform, const FCompactPoseBoneIndex& InBoneIndex, const int32& InTransformIndex) 42 | : Transform(InTransform) 43 | , LocalTransform(InLocalTransform) 44 | , BoneIndex(InBoneIndex) 45 | , TransformIndex(InTransformIndex) 46 | , CurrentAngleDelta(0.f) 47 | { 48 | } 49 | }; 50 | 51 | USTRUCT(BlueprintType) 52 | struct NOBUNANIM_API FAnimNode_SafeCCDIK : public FAnimNode_SkeletalControlBase 53 | { 54 | GENERATED_BODY() 55 | 56 | /** Coordinates for target location of tip bone - if EffectorLocationSpace is bone, this is the offset from Target Bone to use as target location*/ 57 | UPROPERTY(EditAnywhere, Category = Effector, meta = (PinShownByDefault)) 58 | FVector EffectorLocation; 59 | 60 | /** Reference frame of Effector Transform. */ 61 | UPROPERTY(EditAnywhere, Category = Effector) 62 | TEnumAsByte EffectorLocationSpace; 63 | 64 | /** If EffectorTransformSpace is a bone, this is the bone to use. **/ 65 | UPROPERTY(EditAnywhere, Category = Effector) 66 | FBoneSocketTarget EffectorTarget; 67 | 68 | /** Name of tip bone */ 69 | UPROPERTY(EditAnywhere, Category = Solver) 70 | FBoneReference TipBone; 71 | 72 | /** Name of the root bone*/ 73 | UPROPERTY(EditAnywhere, Category = Solver) 74 | FBoneReference RootBone; 75 | 76 | /** Tolerance for final tip location delta from EffectorLocation*/ 77 | UPROPERTY(EditAnywhere, Category = Solver) 78 | float Precision; 79 | 80 | /** Maximum number of iterations allowed, to control performance. */ 81 | UPROPERTY(EditAnywhere, Category = Solver) 82 | int32 MaxIterations; 83 | 84 | /** Toggle drawing of axes to debug joint rotation*/ 85 | UPROPERTY(EditAnywhere, Category = Solver) 86 | bool bStartFromTail; 87 | 88 | /** Tolerance for final tip location delta from EffectorLocation*/ 89 | UPROPERTY(EditAnywhere, Category = Solver) 90 | bool bEnableRotationLimit; 91 | 92 | private: 93 | /** symmetry rotation limit per joint. Index 0 matches with root bone and last index matches with tip bone. */ 94 | UPROPERTY(EditAnywhere, EditFixedSize, Category = Solver) 95 | TArray RotationLimitPerJoints; 96 | 97 | public: 98 | FAnimNode_SafeCCDIK(); 99 | 100 | // FAnimNode_Base interface 101 | virtual void GatherDebugData(FNodeDebugData& DebugData) override; 102 | // End of FAnimNode_Base interface 103 | 104 | // FAnimNode_SkeletalControlBase interface 105 | virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) override; 106 | virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; 107 | // End of FAnimNode_SkeletalControlBase interface 108 | 109 | private: 110 | // FAnimNode_SkeletalControlBase interface 111 | virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; 112 | // End of FAnimNode_SkeletalControlBase interface 113 | 114 | // Convenience function to get current (pre-translation iteration) component space location of bone by bone index 115 | FVector GetCurrentLocation(FCSPose& MeshBases, const FCompactPoseBoneIndex& BoneIndex); 116 | 117 | static FTransform GetTargetTransform(const FTransform& InComponentTransform, FCSPose& MeshBases, FBoneSocketTarget& InTarget, EBoneControlSpace Space, const FVector& InOffset); 118 | 119 | // return true if updated 120 | bool UpdateChainLink(TArray& Chain, int32 LinkIndex, const FVector& TargetPos) const; 121 | public: 122 | #if WITH_EDITOR 123 | #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) 124 | TArray DebugLines; 125 | #endif // #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) 126 | // resize rotation limit array based on set up 127 | void ResizeRotationLimitPerJoints(int32 NewSize); 128 | #endif 129 | }; -------------------------------------------------------------------------------- /Source/Nobunanim/Public/ProceduralGaitControllerComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ProceduralGaitControllerComponent.generated.h" 7 | 8 | class UCurveFloat; 9 | class UGaitDataAsset; 10 | class USkeletalMeshComponent; 11 | class IProceduralGaitInterface; 12 | class UProceduralGaitAnimInstance; 13 | 14 | struct FGaitDebugData; 15 | struct FGaitCorrectionData; 16 | 17 | DECLARE_DYNAMIC_DELEGATE(FSwingEvent); 18 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnEffectorCollision, FName, EffectorName, FVector, ImpactLocation); 19 | 20 | USTRUCT(BlueprintType) 21 | struct FGaitEffectorData 22 | { 23 | GENERATED_BODY() 24 | 25 | /** @to do: Documentation. */ 26 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 27 | FVector CurrentEffectorLocation = FVector::ZeroVector; 28 | /** @to do: Documentation. */ 29 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 30 | FVector IdealEffectorLocation = FVector::ZeroVector; 31 | 32 | /** @to do: Documentation. */ 33 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 34 | FVector GroundLocation = FVector::ZeroVector; 35 | 36 | /** @to do: Documentation. */ 37 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 38 | bool bForceSwing = false; 39 | 40 | /** @to do: Documentation. */ 41 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 42 | bool bCorrectionIK = false; 43 | 44 | /** @to do: Documentation. */ 45 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 46 | float BeginForceSwingInterval = 0; 47 | /** @to do: Documentation. */ 48 | UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 49 | float EndForceSwingInterval = 0; 50 | 51 | //FSwingEvent OnBeginSwing; 52 | //FSwingEvent OnEndSwing; 53 | 54 | //FSwingEvent OnBeginForceSwing; 55 | //FSwingEvent OnEndForceSwing; 56 | 57 | /** @to do: Documentation. */ 58 | float CurrentBlendValue = 0.f; 59 | /** @to do: Documentation. */ 60 | FName CurrentGait; 61 | /** @to do: Documentation. */ 62 | float BlockTime = -1.f; 63 | }; 64 | 65 | 66 | // RENAME AS UProceduralProceduralGaitControllerComponentComponentCOMPONENT 67 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 68 | class NOBUNANIM_API UProceduralGaitControllerComponent : public UActorComponent 69 | { 70 | GENERATED_BODY() 71 | 72 | private: 73 | /** Current gait monde name.*/ 74 | FName CurrentGaitMode; 75 | /** Pending gait mode name. Use to switch gaot with blend time. */ 76 | FName PendingGaitMode = ""; 77 | /** Current time of the cycle (in absolute time [0,1]).*/ 78 | float CurrentTime = 0; 79 | /** Current time buffer used to compute current time.*/ 80 | float TimeBuffer = 0; 81 | /** Owned anim instance. */ 82 | UProceduralGaitAnimInstance* AnimInstanceRef = nullptr; 83 | /** Current LOD.*/ 84 | int32 CurrentLOD = 0; 85 | /** @to do: document. */ 86 | bool bBlendIn = true; 87 | 88 | 89 | 90 | protected: 91 | /** Owned mesh. */ 92 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", VisibleAnywhere, BlueprintReadOnly) 93 | USkeletalMeshComponent* OwnedMesh = nullptr; 94 | 95 | /** Map of Gaits data. Keys are the name of the Gait. */ 96 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadOnly) 97 | TMap GaitsData; 98 | 99 | 100 | /** Current playrate of the cycle.*/ 101 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.001", ClampMax = "10", SliderMin = "0.001", SliderMax = "10.f")) 102 | float PlayRate = 1.f; 103 | 104 | /** Current playrate of the cycle.*/ 105 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.001", ClampMax = "10", SliderMin = "0.001", SliderMax = "10.f")) 106 | bool bGaitActive = true; 107 | 108 | /** Show effector debug. */ 109 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", EditAnywhere, BlueprintReadWrite) 110 | bool bShowDebug = false; 111 | 112 | 113 | #if WITH_EDITORONLY_DATA 114 | /** Show effector debug. */ 115 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", EditAnywhere, BlueprintReadWrite) 116 | bool bShowLOD = false; 117 | #endif 118 | 119 | protected: 120 | /** Map of Gaits data. Keys are the name of the Gait. */ 121 | TMap Effectors; 122 | 123 | /** @to do: documentation. */ 124 | FVector LastVelocity; 125 | 126 | /** */ 127 | bool bLastFrameWasDisable = true; 128 | 129 | 130 | public: 131 | // Sets default values for this component's properties 132 | UProceduralGaitControllerComponent(); 133 | 134 | protected: 135 | // Called when the game starts 136 | virtual void BeginPlay() override; 137 | 138 | public: 139 | // Called every frame 140 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 141 | 142 | UFUNCTION(Category = "[NOBUNANIM]|Gait Controller", BlueprintNativeEvent, BlueprintCallable) 143 | void UpdateGaitMode(const FName& NewGaitName); 144 | 145 | /** Called each time than an effector that must raise the event enter in collision. */ 146 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", BlueprintAssignable) 147 | FOnEffectorCollision OnCollisionEvent; 148 | 149 | 150 | #if WITH_EDITOR 151 | /** [NOBUNANIM] Toggle procedural gait debug for this actor. */ 152 | UFUNCTION(Exec, Category = "[NOBUNANIM]|Gait Controller") 153 | void ShowGaitDebug(); 154 | 155 | /** [NOBUNANIM] Toggle procedural gait LOD debug for this actor. */ 156 | UFUNCTION(Exec, Category = "[NOBUNANIM]|Gait Controller") 157 | void ShowGaitLOD(); 158 | #endif 159 | 160 | 161 | private: 162 | /** .*/ 163 | void ComputeCollisionCorrection(const FGaitCorrectionData* CorrectionData, FGaitEffectorData& Effector); 164 | 165 | /** Update effectors data.*/ 166 | void UpdateEffectors(const UGaitDataAsset& CurrentAsset); 167 | 168 | /** AARJHALJKDHFLKJDAHL(some kind of dying scream). */ 169 | bool IsInRange(float Value, float Min, float Max, float& OutRangeMin, float& OutRangeMax); 170 | 171 | void DrawGaitDebug(FVector Position, FVector EffectorLocation, FVector CurrentLocation, float Treshold, bool bAutoAdjustWithIdealEffector, bool bForceSwing, const FGaitDebugData* DebugData); 172 | 173 | void UpdateLOD(bool bForceUpdate = false); 174 | 175 | bool TraceRay(UWorld* World, TArray& HitResults, FVector Origin, FVector Dest, TEnumAsByte TraceChannel, float SphereCastRadius); 176 | 177 | FHitResult& GetBestHitResult(TArray& HitResults, FVector IdealLocation); 178 | 179 | //FVector RotateToVelocity(FVector Input); 180 | }; 181 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/ProceduralAnimator.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Nobunanim/Public/ProceduralGaitInterface.h" 11 | #include "Nobunanim/Public/ProceduralAnimAsset.h" 12 | 13 | #include "ProceduralAnimator.generated.h" 14 | 15 | 16 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FProceduralAnimAssetEvent, FName, _EventName); 17 | 18 | //class ULocomotionComponent; 19 | class USkeletalMeshComponent; 20 | class UCurveFloat; 21 | class UCurveVector; 22 | 23 | 24 | USTRUCT(BlueprintType) 25 | struct FEffectorAnimData 26 | { 27 | GENERATED_BODY() 28 | 29 | public: 30 | /** */ 31 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 32 | FVector IdLocation; 33 | 34 | /** */ 35 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 36 | FVector CurrentDestination; 37 | 38 | bool bStartSwing = false; 39 | 40 | /** */ 41 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 42 | FVector CurrentLocation; 43 | 44 | /** */ 45 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 46 | bool bForceSwing; 47 | /** */ 48 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 49 | float currentEndSwing; 50 | /** */ 51 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 52 | float currentBeginSwing; 53 | }; 54 | 55 | // This class does not need to be modified. 56 | UCLASS() 57 | class NOBUNANIM_API UProceduralAnimator : public UObject, public IProceduralGaitInterface 58 | { 59 | GENERATED_BODY() 60 | 61 | private: 62 | /** REFERENCES 63 | */ 64 | /** World. */ 65 | UWorld* world; 66 | /** Skeletal Mesh reference. */ 67 | USkeletalMeshComponent* mesh; 68 | /** Locomotion component reference. */ 69 | //ULocomotionComponent* locomotion; 70 | 71 | /** Deltatime evaluated each Evaluate_Internal(). */ 72 | float deltaTime = 0.f; 73 | /** Buffer to calculate deltatime. */ 74 | float lastTime = 0.f; 75 | /** Current direction based on locomotion component. Range is [-180;180] in degree rad. */ 76 | int direction = 0.f; 77 | /** Cached last velocity. */ 78 | FVector lastVelocity = FVector::ZeroVector; 79 | 80 | /** Procedural anim asset raised event buffer. */ 81 | TMap> PAAEventRaised; 82 | 83 | 84 | protected: 85 | /** Current procedural animation to evaluate. */ 86 | TMap currentAnimSet; 87 | /** Map to manage adding/removing anim. */ 88 | TMap timerHandles; 89 | 90 | /** Effectors. */ 91 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 92 | TMap effectors; 93 | /** Map to manage anim asset time buffers. */ 94 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 95 | TMap timeBuffers; 96 | /** Map to manage anim asset frame refresh rate. */ 97 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 98 | TMap refreshRateBuffers; 99 | 100 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 101 | TMap effectorsLocation; 102 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 103 | TMap effectorsRotation; 104 | 105 | int updateCount; 106 | 107 | /** TimerHandle for internal update. */ 108 | FTimerHandle updateTimerHandle; 109 | 110 | /** Animations total weight. */ 111 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 112 | float globalWeight = 0.0f ; 113 | /** Current LOD. */ 114 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 115 | int32 currentLOD; 116 | /** Use to init check. */ 117 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 118 | bool bIsInitialized = false; 119 | /** Is procedural animator enabled? */ 120 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 121 | bool bEnable; 122 | /** Is procedural animator running evaluations? */ 123 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintReadOnly) 124 | bool bEvaluationActive; 125 | /** Global animation play rate of Procedural Animator. */ 126 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", VisibleAnywhere, BlueprintReadOnly) 127 | float playRate = 1.f; 128 | 129 | /** May the belocity be conserved instead of being set to Zero. */ 130 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", EditAnywhere, BlueprintReadWrite) 131 | bool bConserveVelocityIfZero = true; 132 | 133 | /** Swing if to far time treshold. */ 134 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", EditAnywhere, BlueprintReadWrite) 135 | float swingTimeTreshold = 0.095f; 136 | 137 | public: 138 | /** Multicast raised from procedural anim assets. */ 139 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Animator", BlueprintAssignable) 140 | FProceduralAnimAssetEvent OnProceduralAnimAssetEvent; 141 | 142 | 143 | public: 144 | /** Init the animator. */ 145 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Animator", BlueprintCallable) 146 | void Initialize(USkeletalMeshComponent* _Mesh /*,ULocomotionComponent* _Locomotion = nullptr*/); 147 | /** Inject procedural animation for a duration. */ 148 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Animator", BlueprintCallable) 149 | void Add(UProceduralAnimAsset* _Anim, int _FrameDuration, bool bCached = true); 150 | /** Remove the given procedural animation asset from the active anim set. */ 151 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Animator", BlueprintCallable) 152 | void Remove(UProceduralAnimAsset* _Anim); 153 | /** Remove the given key from the active anim set. */ 154 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Animator", BlueprintCallable) 155 | void RemoveFromName(FName _Key); 156 | 157 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Animator", BlueprintCallable) 158 | void SetActive(bool _bEnable); 159 | 160 | protected: 161 | /** .*/ 162 | virtual void UpdateEffectorTranslation_Implementation(const FName& _Socket, FVector _Translation, bool _bLerp, float _LerpSpeed) override; 163 | virtual void UpdateEffectorRotation_Implementation(const FName& _Socket, FRotator _Rotation, float _LerpSpeed) override; 164 | virtual void SetProceduralGaitEnable_Implementation(bool _bEnable) override; 165 | 166 | 167 | private: 168 | /** Global update. */ 169 | void Evaluate_Internal(); 170 | /** Per anim asset evaluation. */ 171 | void EvaluateAnimSet(); 172 | /** Update effector ideal location. */ 173 | void UpdateEffectors(); 174 | /** Per effector evaluation. */ 175 | void EvaluateEffector(const UProceduralAnimAsset* _AnimAsset, const FProceduralAnimData& _AnimData, const FName& _EffectorName, float _CurrentTime); 176 | 177 | /** Update global weight based on all active procedural anim weight. */ 178 | void UpdateGlobalWeight(); 179 | 180 | /** Return if a value is in the range [0;1] and output adjusted ranges. */ 181 | bool IsInSwingRange(float _Value, float _Min, float _Max, float& out_RangeMin, float& out_RangeMax); 182 | }; 183 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/ProceduralGaitAnimInstance.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Containers/Map.h" 6 | #include "ProceduralGaitInterface.h" 7 | 8 | #include "Nobunanim/Public/ProceduralGaitControllerComponent.h" 9 | #include "Animation/AnimInstance.h" 10 | #include "ProceduralGaitAnimInstance.generated.h" 11 | 12 | 13 | class UCurveFloat; 14 | class UGaitDataAsset; 15 | 16 | 17 | //USTRUCT(BlueprintType) 18 | //struct FGaitEffectorData 19 | //{ 20 | // GENERATED_BODY() 21 | // 22 | // /** @to do: Documentation. */ 23 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 24 | // FVector CurrentEffectorLocation = FVector::ZeroVector; 25 | // /** @to do: Documentation. */ 26 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 27 | // FVector IdealEffectorLocation = FVector::ZeroVector; 28 | // 29 | // /** @to do: Documentation. */ 30 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 31 | // FVector GroundLocation = FVector::ZeroVector; 32 | // 33 | // /** @to do: Documentation. */ 34 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 35 | // bool bForceSwing = false; 36 | // 37 | // /** @to do: Documentation. */ 38 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 39 | // bool bCorrectionIK = false; 40 | // 41 | // /** @to do: Documentation. */ 42 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 43 | // float BeginForceSwingInterval = 0; 44 | // /** @to do: Documentation. */ 45 | // UPROPERTY(Category = "[NOBUNANIM]|Effector Data", EditAnywhere, BlueprintReadWrite) 46 | // float EndForceSwingInterval = 0; 47 | // 48 | // //FSwingEvent OnBeginSwing; 49 | // //FSwingEvent OnEndSwing; 50 | // 51 | // //FSwingEvent OnBeginForceSwing; 52 | // //FSwingEvent OnEndForceSwing; 53 | // 54 | // /** @to do: Documentation. */ 55 | // float CurrentBlendValue = 0.f; 56 | // /** @to do: Documentation. */ 57 | // FName CurrentGait; 58 | // /** @to do: Documentation. */ 59 | // float BlockTime = -1.f; 60 | //}; 61 | 62 | //USTRUCT() 63 | struct FProceduralGaitAnimInstanceTickFunction : public FTickFunction 64 | { 65 | //GENERATED_BODY() 66 | 67 | /** AActor that is the target of this tick **/ 68 | UProceduralGaitAnimInstance* Target; 69 | 70 | /** 71 | * Abstract function actually execute the tick. 72 | * @param DeltaTime - frame time to advance, in seconds 73 | * @param TickType - kind of tick for this frame 74 | * @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created 75 | * @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete. 76 | **/ 77 | virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; 78 | /** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/ 79 | virtual FString DiagnosticMessage() override; 80 | virtual FName DiagnosticContext(bool bDetailed) override; 81 | }; 82 | 83 | /** 84 | */ 85 | USTRUCT(BlueprintType) 86 | struct FGroundReflectionSocketData 87 | { 88 | GENERATED_BODY() 89 | 90 | /** @to do: documentation. */ 91 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 92 | FName FrontSocket; 93 | 94 | /** @to do: documentation. */ 95 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 96 | FName BackSocket; 97 | 98 | /** @to do: documentation. */ 99 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 100 | FName RightSocket; 101 | 102 | /** @to do: documentation. */ 103 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadWrite) 104 | FName LeftSocket; 105 | 106 | /** bShowDebug. */ 107 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", EditAnywhere, BlueprintReadWrite) 108 | bool bShowDebugPlanes = true; 109 | 110 | /** Should use half of the plane's vectors. */ 111 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", EditAnywhere, BlueprintReadWrite) 112 | bool bUseHalfVector = false; 113 | 114 | /** Lerp speed of effectors. */ 115 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", EditAnywhere, BlueprintReadWrite) 116 | float GroundReflectionLerpSpeed = 10.f; 117 | }; 118 | 119 | 120 | /** 121 | * Only manage 122 | */ 123 | UCLASS() 124 | class NOBUNANIM_API UProceduralGaitAnimInstance : public UAnimInstance, public IProceduralGaitInterface 125 | { 126 | GENERATED_BODY() 127 | 128 | 129 | protected: 130 | /** Current gait monde name.*/ 131 | FName CurrentGaitMode; 132 | /** Pending gait mode name. Use to switch gaot with blend time. */ 133 | FName PendingGaitMode = ""; 134 | /** Current time of the cycle (in absolute time [0,1]).*/ 135 | float CurrentTime = 0; 136 | /** Current time buffer used to compute current time.*/ 137 | float TimeBuffer = 0; 138 | /** Current LOD.*/ 139 | int32 CurrentLOD = 0; 140 | 141 | /** Map of Gaits data. Keys are the name of the Gait. */ 142 | TMap Effectors; 143 | /** @to do: documentation. */ 144 | FVector LastVelocity; 145 | /** */ 146 | //bool bLastFrameWasDisable = true; 147 | bool bUpdateGaitActive = false; 148 | 149 | 150 | protected: 151 | /** Owned mesh. */ 152 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", VisibleAnywhere, BlueprintReadOnly) 153 | USkeletalMeshComponent* OwnedMesh = nullptr; 154 | 155 | /** Map of Gaits data. Keys are the name of the Gait. */ 156 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadOnly) 157 | TMap GaitsData; 158 | 159 | 160 | /** Current playrate of the cycle.*/ 161 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.001", ClampMax = "10", SliderMin = "0.001", SliderMax = "10.f")) 162 | float PlayRate = 1.f; 163 | 164 | /** Current playrate of the cycle.*/ 165 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.001", ClampMax = "10", SliderMin = "0.001", SliderMax = "10.f")) 166 | bool bGaitActive = true; 167 | 168 | /** Show effector debug. */ 169 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", EditAnywhere, BlueprintReadWrite) 170 | bool bShowDebug = false; 171 | 172 | 173 | #if WITH_EDITORONLY_DATA 174 | /** Show effector debug. */ 175 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", EditAnywhere, BlueprintReadWrite) 176 | bool bShowLOD = false; 177 | #endif 178 | 179 | 180 | public: 181 | //UPROPERTY(EditDefaultsOnly, Category = Tick) 182 | FProceduralGaitAnimInstanceTickFunction PrimaryAnimInstanceTick; 183 | /** @todo: documentation. */ 184 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", VisibleAnywhere, BlueprintReadOnly) 185 | TMap EffectorsTranslation; 186 | /** @todo: documentation. */ 187 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", VisibleAnywhere, BlueprintReadOnly) 188 | TMap BonesRotation; 189 | 190 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance|Ground Reflection", EditAnywhere, BlueprintReadOnly) 191 | FGroundReflectionSocketData GroundReflection; 192 | 193 | /** .*/ 194 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadOnly) 195 | FRotator GroundReflectionRotation; 196 | 197 | UPROPERTY(Category = "[NOBUNANIM]|Gait Data|Ground reflection", EditAnywhere, BlueprintReadOnly) 198 | FVector RayVector = FVector(0,0, -500.f); 199 | 200 | /** Lerp speed of effectors. */ 201 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", EditAnywhere, BlueprintReadWrite) 202 | float GaitActive = 0.f; 203 | 204 | /** Lerp speed of effectors. */ 205 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Gait Anim Instance", EditAnywhere, BlueprintReadWrite) 206 | float GroundReflectionLerpSpeed = 10.f; 207 | 208 | /** Called each time than an effector that must raise the event enter in collision. */ 209 | UPROPERTY(Category = "[NOBUNANIM]|Gait Controller|Debug", BlueprintAssignable) 210 | FOnEffectorCollision OnCollisionEvent; 211 | 212 | 213 | private: 214 | float DeltaTime = 0.f; 215 | /** Last time from procedural gait update. Use to compute deltatime. */ 216 | float LastTime = 0.f; 217 | /** The timer used for deferred gaits update. */ 218 | FTimerHandle GaitUpdateTimer; 219 | 220 | //USkeletalMeshComponent* OwnedMesh; 221 | /** Current LOD.*/ 222 | //int32 CurrentLOD = 0; 223 | 224 | 225 | public: 226 | /** UNREAL METHODS 227 | */ 228 | // Native update override point. It is usually a good idea to simply gather data in this step and 229 | // for the bulk of the work to be done in NativeUpdateAnimation. 230 | virtual void NativeUpdateAnimation(float DeltaSeconds) override; 231 | virtual void NativeBeginPlay() override; 232 | 233 | public: 234 | /** PROCEDURAL GAIT INTERFACE 235 | */ 236 | void UpdateEffectorTranslation_Implementation(const FName& TargetBone, FVector Translation, bool bLerp, float LerpSpeed) override; 237 | void UpdateEffectorRotation_Implementation(const FName& TargetBone, FRotator Rotation, float LerpSpeed) override; 238 | void SetProceduralGaitEnable_Implementation(bool bEnable) override; 239 | 240 | /** Update of procedural gait. */ 241 | void virtual ProceduralGaitUpdate(); 242 | //void virtual ProceduralGaitUpdate(float DeltaTime); 243 | 244 | 245 | protected: 246 | UFUNCTION(Category = "[NOBUNANIM]|Gait Controller", BlueprintNativeEvent, BlueprintCallable) 247 | void UpdateGaitMode(const FName& NewGaitName); 248 | 249 | private: 250 | /** TERRAIN PREDICTION UTILITIES 251 | */ 252 | FRotator GetPlaneRotation(FVector A, FVector B, FVector C, FVector RightVector, FRotator& OutRotation, bool bComputeHalf, bool bShowDebug); 253 | FVector TraceGroundRaycast(UWorld* World, FVector Origin, FVector Dest); 254 | 255 | 256 | FRotator ComputeGroundReflection_LOD0(); 257 | FRotator ComputeGroundReflection_LOD1(); 258 | 259 | 260 | private: 261 | /** PROCEDURAL GAIT UTILITIES 262 | */ 263 | /** Trace complexe ray... */ 264 | bool TraceRay(UWorld* World, TArray& HitResults, FVector Origin, FVector Dest, TEnumAsByte TraceChannel, float SphereCastRadius); 265 | 266 | /** Get best hit result according to the ideal location.*/ 267 | FHitResult& GetBestHitResult(TArray& HitResults, FVector IdealLocation); 268 | 269 | /** .*/ 270 | void ComputeCollisionCorrection(const FGaitCorrectionData* CorrectionData, FGaitEffectorData& Effector); 271 | 272 | /** Update effectors data.*/ 273 | void UpdateEffectors(const UGaitDataAsset& CurrentAsset); 274 | 275 | /** AARJHALJKDHFLKJDAHL(some kind of dying scream). */ 276 | bool IsInRange(float Value, float Min, float Max, float& OutRangeMin, float& OutRangeMax); 277 | 278 | void DrawGaitDebug(FVector Position, FVector EffectorLocation, FVector CurrentLocation, float Treshold, bool bAutoAdjustWithIdealEffector, bool bForceSwing, const FGaitDebugData* DebugData); 279 | 280 | void UpdateLOD(bool bForceUpdate = false); 281 | 282 | void SetProceduralGaitUpdateEnable(bool bEnable); 283 | }; 284 | -------------------------------------------------------------------------------- /Source/Nobunanim/Public/ProceduralGaitInterface.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "UObject/Interface.h" 8 | #include "ProceduralGaitInterface.generated.h" 9 | 10 | class UCurveFloat; 11 | class UCurveVector; 12 | 13 | /** 14 | */ 15 | USTRUCT(BlueprintType) 16 | struct FGaitSwingTimeData 17 | { 18 | GENERATED_BODY() 19 | 20 | ///** Begin of the swing in absolute time (0-1).*/ 21 | //UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 22 | //float BeginSwing = 0.f; 23 | ///** End of the swing in absolute time (0-1).*/ 24 | //UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 25 | //float EndSwing = 1.f; 26 | 27 | /** The name of the effector to link to. If valid, this effector will swing at the same time as the parent. Begin swing and End swing will be considered as offset (i.e. EndSwing = 0.1 will end at parent EndSwing + 0.1)*/ 28 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 29 | FName ParentEffector; 30 | }; 31 | 32 | /** 33 | */ 34 | USTRUCT(BlueprintType) 35 | struct FGaitBlendData 36 | { 37 | GENERATED_BODY() 38 | 39 | /** Blend in time in second. */ 40 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Blend", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "5", SliderMin = "0", SliderMax = "5")) 41 | float BlendInTime = 0.f; 42 | 43 | /** Blend out time in second. */ 44 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Blend", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "5", SliderMin = "0", SliderMax = "5")) 45 | float BlendOutTime = 0.f; 46 | 47 | /** @LerpSpeed acceleration during blend in. */ 48 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Blend", EditAnywhere, BlueprintReadWrite) 49 | UCurveFloat* BlendInAcceleration; 50 | 51 | /** @LerpSpeed acceleration during blend out. */ 52 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Blend", EditAnywhere, BlueprintReadWrite) 53 | UCurveFloat* BlendOutAcceleration; 54 | }; 55 | 56 | USTRUCT(BlueprintType) 57 | struct FGaitEventData 58 | { 59 | GENERATED_BODY() 60 | 61 | /** Should this bone raise the event @OnEffectorCollision of the ProceduralGaitComponent. */ 62 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Evet", EditAnywhere, BlueprintReadWrite) 63 | bool bRaiseOnCollisionEvent; 64 | }; 65 | 66 | /** 67 | */ 68 | USTRUCT(BlueprintType) 69 | struct FGaitTranslationData 70 | { 71 | GENERATED_BODY() 72 | 73 | /** Should the translation affect the effector? Usefull for non destructive operation. */ 74 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 75 | bool bAffectEffector = true; 76 | 77 | /** Transform space to compute effector. */ 78 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 79 | TEnumAsByte TransformSpace = ERelativeTransformSpace::RTS_World; 80 | 81 | /** Swing curve. This value is FVector(1,1,1) if the curve is not set. */ 82 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 83 | UCurveVector* SwingTranslationCurve; 84 | 85 | /** Scale of the swing curve. */ 86 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 87 | float TranslationSwingScale = 1.f; 88 | 89 | /** Lerp speed of the translation. Null or Negative lerp speed means no lerp at all. */ 90 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 91 | float LerpSpeed = 10.f; 92 | 93 | /** Factor to apply on translation swing. */ 94 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 95 | FVector TranslationFactor = FVector(1.f, 1.f, 1.f); 96 | 97 | /** @to do: Documentation. */ 98 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 99 | FVector Offset; 100 | 101 | /** Should the translation be oriented in the direction of the speed or the front of the actor? */ 102 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 103 | bool bOrientToVelocity = true; 104 | 105 | /* @to do: documentation. */ 106 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 107 | bool bAdaptToGroundLevel = true; 108 | 109 | /** The socket to take as ref for the ground adaptation. */ 110 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 111 | FName GroundReferenceSocket; 112 | 113 | /** @to do: Documentation. */ 114 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 115 | float GroundReferenceZOffset = -20.f; 116 | }; 117 | 118 | 119 | /** 120 | */ 121 | USTRUCT(BlueprintType) 122 | struct FGaitRotationData 123 | { 124 | GENERATED_BODY() 125 | 126 | /** Should the translation affect the effector? Usefull for non destructive operation. */ 127 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 128 | bool bAffectEffector = false; 129 | 130 | /** @to do: Documentation. */ 131 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Rotation", EditAnywhere, BlueprintReadWrite) 132 | UCurveVector* SwingRotationCurve; 133 | 134 | /** Factor to apply on rotation swing.*/ 135 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Rotation", EditAnywhere, BlueprintReadWrite) 136 | FVector RotationFactor = FVector(1.f, 1.f, 1.f); 137 | }; 138 | 139 | 140 | /** 141 | */ 142 | USTRUCT(BlueprintType) 143 | struct FGaitCorrectionData 144 | { 145 | GENERATED_BODY() 146 | 147 | /** May the swing be adjust if too far from ideal effector. */ 148 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 149 | bool bAutoAdjustWithIdealEffector = true; 150 | 151 | /** Distance treshold to adjust (if enabled) if too far from ideal effector. */ 152 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 153 | float DistanceTresholdToAdjust = 100.f; 154 | 155 | /** Swing curve. This value is FVector(1,1,1) if the curve is not set. */ 156 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Translation", EditAnywhere, BlueprintReadWrite) 157 | UCurveVector* CorrectionSwingTranslationCurve; 158 | 159 | /** Should this effector take collisions into account? */ 160 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 161 | bool bComputeCollision; 162 | 163 | /** Should this effector take collisions into account? */ 164 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 165 | TEnumAsByte TraceChannel; 166 | 167 | /** @to do: documentation. */ 168 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 169 | bool bUseCurrentEffector = false; 170 | 171 | /** The name of the socket or bone to use as origin of the sweep. "None" will result to the use of the effector location. */ 172 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 173 | FName OriginCollisionSocketName; 174 | 175 | /** @to do: documentation. */ 176 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 177 | FVector CollisionSnapOffset; 178 | 179 | 180 | /** @to do: documentation. */ 181 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 182 | FVector AbsoluteDirection = FVector(0,0,-10.f); 183 | 184 | 185 | /** @to do: documentation. 1: rotation, 2: velocity, 3: absolute direction. */ 186 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|New|Correction", EditAnywhere, BlueprintReadWrite) 187 | bool bOrientToVelocity = false; 188 | }; 189 | 190 | 191 | /** 192 | */ 193 | USTRUCT(BlueprintType) 194 | struct FGaitDebugData 195 | { 196 | GENERATED_BODY() 197 | 198 | /** May this gait render debug. (require to call cmd: ShowGaitDebug) */ 199 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Debug", EditAnywhere, BlueprintReadWrite) 200 | bool bDrawDebug = true; 201 | 202 | /** Color of the correction circle. */ 203 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Debug", EditAnywhere, BlueprintReadWrite) 204 | FColor CorrectionCircleColor = FColor::Orange; 205 | 206 | /** Color of the correction circle. */ 207 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Debug", EditAnywhere, BlueprintReadWrite) 208 | FColor ForceSwingColor = FColor::Red; 209 | 210 | /** Color of the correction circle. */ 211 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data|Debug", EditAnywhere, BlueprintReadWrite) 212 | FColor VelocityColor = FColor::Purple; 213 | }; 214 | 215 | 216 | /** 217 | */ 218 | USTRUCT(BlueprintType) 219 | struct FGaitSwingData 220 | { 221 | GENERATED_BODY() 222 | 223 | /** Begin of the swing in absolute time (0-1).*/ 224 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 225 | float BeginSwing = 0.f; 226 | /** End of the swing in absolute time (0-1).*/ 227 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 228 | float EndSwing = 1.f; 229 | 230 | /** Swing time info. */ 231 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 232 | FGaitSwingTimeData SwingTime; 233 | 234 | /** @to do: Documentation. */ 235 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 236 | FGaitBlendData BlendData; 237 | 238 | /** @to do: Documentation. */ 239 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 240 | FGaitEventData EventData; 241 | 242 | /** @to do: Documentation. */ 243 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 244 | FGaitTranslationData TranslationData; 245 | 246 | /** @to do: Documentation. */ 247 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 248 | FGaitRotationData RotationData; 249 | 250 | /** @to do: Documentation. */ 251 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 252 | FGaitCorrectionData CorrectionData; 253 | 254 | /** @to do: Documentation. */ 255 | UPROPERTY(Category = "[NOBUNANIM]|Gait Swing Data", EditAnywhere, BlueprintReadWrite) 256 | FGaitDebugData DebugData; 257 | 258 | }; 259 | 260 | // This class does not need to be modified. 261 | UINTERFACE(MinimalAPI) 262 | class UProceduralGaitInterface : public UInterface 263 | { 264 | GENERATED_BODY() 265 | }; 266 | 267 | /** 268 | * 269 | */ 270 | class NOBUNANIM_API IProceduralGaitInterface 271 | { 272 | GENERATED_BODY() 273 | 274 | // Add interface functions to this class. This is the class that will be inherited to implement this interface. 275 | public: 276 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Gait Interface", BlueprintNativeEvent, BlueprintCallable) 277 | void UpdateEffectorTranslation(const FName& TargetBone, FVector Translation, bool bLerp = true, float LerpSpeed = 10.f); 278 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Gait Interface", BlueprintNativeEvent, BlueprintCallable) 279 | void UpdateEffectorRotation(const FName& TargetBone, FRotator Rotation, float LerpSpeed = 10.f); 280 | 281 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Gait Interface", BlueprintNativeEvent, BlueprintCallable) 282 | void SetProceduralGaitEnable(bool bEnable); 283 | }; 284 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/AnimNodes/AnimNode_SafeCCDIK.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "../../Public/AnimNodes/AnimNode_SafeCCDIK.h" 4 | #include "Animation/AnimTypes.h" 5 | #include "AnimationRuntime.h" 6 | #include "DrawDebugHelpers.h" 7 | #include "Animation/AnimInstanceProxy.h" 8 | 9 | PRAGMA_DISABLE_OPTIMIZATION 10 | ///////////////////////////////////////////////////// 11 | // AnimNode_CCDIK 12 | // Implementation of the CCDIK IK Algorithm 13 | 14 | FAnimNode_SafeCCDIK::FAnimNode_SafeCCDIK() 15 | : EffectorLocation(FVector::ZeroVector) 16 | , EffectorLocationSpace(BCS_ComponentSpace) 17 | , Precision(1.f) 18 | , MaxIterations(10) 19 | , bStartFromTail(true) 20 | , bEnableRotationLimit(false) 21 | { 22 | } 23 | 24 | FVector FAnimNode_SafeCCDIK::GetCurrentLocation(FCSPose& MeshBases, const FCompactPoseBoneIndex& BoneIndex) 25 | { 26 | return MeshBases.GetComponentSpaceTransform(BoneIndex).GetLocation(); 27 | } 28 | 29 | FTransform FAnimNode_SafeCCDIK::GetTargetTransform(const FTransform& InComponentTransform, FCSPose& MeshBases, FBoneSocketTarget& InTarget, EBoneControlSpace Space, const FVector& InOffset) 30 | { 31 | FTransform OutTransform; 32 | if (Space == BCS_BoneSpace) 33 | { 34 | OutTransform = InTarget.GetTargetTransform(InOffset, MeshBases, InComponentTransform); 35 | } 36 | else 37 | { 38 | // parent bone space still goes through this way 39 | // if your target is socket, it will try find parents of joint that socket belongs to 40 | OutTransform.SetLocation(InOffset); 41 | FAnimationRuntime::ConvertBoneSpaceTransformToCS(InComponentTransform, MeshBases, OutTransform, InTarget.GetCompactPoseBoneIndex(), Space); 42 | } 43 | 44 | return OutTransform; 45 | } 46 | 47 | void FAnimNode_SafeCCDIK::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) 48 | { 49 | const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); 50 | 51 | // Update EffectorLocation if it is based off a bone position 52 | FTransform CSEffectorTransform = GetTargetTransform(Output.AnimInstanceProxy->GetComponentTransform(), Output.Pose, EffectorTarget, EffectorLocationSpace, EffectorLocation); 53 | FVector const CSEffectorLocation = CSEffectorTransform.GetLocation(); 54 | 55 | // Gather all bone indices between root and tip. 56 | TArray BoneIndices; 57 | 58 | { 59 | const FCompactPoseBoneIndex RootIndex = RootBone.GetCompactPoseIndex(BoneContainer); 60 | FCompactPoseBoneIndex BoneIndex = TipBone.GetCompactPoseIndex(BoneContainer); 61 | do 62 | { 63 | BoneIndices.Insert(BoneIndex, 0); 64 | BoneIndex = Output.Pose.GetPose().GetParentBoneIndex(BoneIndex); 65 | } while (BoneIndex != RootIndex); 66 | BoneIndices.Insert(BoneIndex, 0); 67 | } 68 | 69 | // Gather transforms 70 | int32 const NumTransforms = BoneIndices.Num(); 71 | OutBoneTransforms.AddUninitialized(NumTransforms); 72 | 73 | // Gather chain links. These are non zero length bones. 74 | TArray Chain; 75 | Chain.Reserve(NumTransforms); 76 | // Start with Root Bone 77 | { 78 | const FCompactPoseBoneIndex& RootBoneIndex = BoneIndices[0]; 79 | const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(RootBoneIndex); 80 | const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(RootBoneIndex); 81 | 82 | OutBoneTransforms[0] = FBoneTransform(RootBoneIndex, BoneCSTransform); 83 | Chain.Add(SafeCCDIKChainLink(BoneCSTransform, LocalTransform, RootBoneIndex, 0)); 84 | } 85 | 86 | // Go through remaining transforms 87 | for (int32 TransformIndex = 1; TransformIndex < NumTransforms; TransformIndex++) 88 | { 89 | const FCompactPoseBoneIndex& BoneIndex = BoneIndices[TransformIndex]; 90 | 91 | const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(BoneIndex); 92 | const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(BoneIndex); 93 | FVector const BoneCSPosition = BoneCSTransform.GetLocation(); 94 | 95 | OutBoneTransforms[TransformIndex] = FBoneTransform(BoneIndex, BoneCSTransform); 96 | 97 | // Calculate the combined length of this segment of skeleton 98 | float const BoneLength = FVector::Dist(BoneCSPosition, OutBoneTransforms[TransformIndex - 1].Transform.GetLocation()); 99 | 100 | if (!FMath::IsNearlyZero(BoneLength)) 101 | { 102 | Chain.Add(SafeCCDIKChainLink(BoneCSTransform, LocalTransform, BoneIndex, TransformIndex)); 103 | } 104 | else 105 | { 106 | // Mark this transform as a zero length child of the last link. 107 | // It will inherit position and delta rotation from parent link. 108 | SafeCCDIKChainLink & ParentLink = Chain[Chain.Num() - 1]; 109 | ParentLink.ChildZeroLengthTransformIndices.Add(TransformIndex); 110 | } 111 | } 112 | 113 | bool bBoneLocationUpdated = false; 114 | int32 const NumChainLinks = BoneIndices.Num(); 115 | 116 | // iterate 117 | { 118 | int32 const TipBoneLinkIndex = NumChainLinks - 1; 119 | 120 | // @todo optimize locally if no update, stop? 121 | bool bLocalUpdated = false; 122 | // check how far 123 | const FVector TargetPos = CSEffectorLocation; 124 | 125 | if (Chain.Num() <= TipBoneLinkIndex) 126 | { 127 | // prevent crash, skip this frame 128 | return; 129 | } 130 | 131 | FVector TipPos = Chain[TipBoneLinkIndex].Transform.GetLocation(); 132 | float Distance = FVector::Dist(TargetPos, TipPos); 133 | int32 IterationCount = 0; 134 | while ((Distance > Precision) && (IterationCount++ < MaxIterations)) 135 | { 136 | // iterate from tip to root 137 | if (bStartFromTail) 138 | { 139 | for (int32 LinkIndex = TipBoneLinkIndex - 1; LinkIndex > 0; --LinkIndex) 140 | { 141 | bLocalUpdated |= UpdateChainLink(Chain, LinkIndex, TargetPos); 142 | } 143 | } 144 | else 145 | { 146 | for (int32 LinkIndex = 1; LinkIndex < TipBoneLinkIndex; ++LinkIndex) 147 | { 148 | bLocalUpdated |= UpdateChainLink(Chain, LinkIndex, TargetPos); 149 | } 150 | } 151 | 152 | Distance = FVector::Dist(Chain[TipBoneLinkIndex].Transform.GetLocation(), CSEffectorLocation); 153 | 154 | bBoneLocationUpdated |= bLocalUpdated; 155 | 156 | // no more update in this iteration 157 | if (!bLocalUpdated) 158 | { 159 | break; 160 | } 161 | } 162 | } 163 | 164 | // If we moved some bones, update bone transforms. 165 | if (bBoneLocationUpdated) 166 | { 167 | // First step: update bone transform positions from chain links. 168 | for (int32 LinkIndex = 0; LinkIndex < NumChainLinks; LinkIndex++) 169 | { 170 | SafeCCDIKChainLink const & ChainLink = Chain[LinkIndex]; 171 | OutBoneTransforms[ChainLink.TransformIndex].Transform = ChainLink.Transform; 172 | 173 | // If there are any zero length children, update position of those 174 | int32 const NumChildren = ChainLink.ChildZeroLengthTransformIndices.Num(); 175 | for (int32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++) 176 | { 177 | OutBoneTransforms[ChainLink.ChildZeroLengthTransformIndices[ChildIndex]].Transform = ChainLink.Transform; 178 | } 179 | } 180 | 181 | #if WITH_EDITOR 182 | DebugLines.Reset(OutBoneTransforms.Num()); 183 | DebugLines.AddUninitialized(OutBoneTransforms.Num()); 184 | for (int32 Index = 0; Index < OutBoneTransforms.Num(); ++Index) 185 | { 186 | DebugLines[Index] = OutBoneTransforms[Index].Transform.GetLocation(); 187 | } 188 | #endif // WITH_EDITOR 189 | 190 | } 191 | } 192 | 193 | bool FAnimNode_SafeCCDIK::UpdateChainLink(TArray& Chain, int32 LinkIndex, const FVector& TargetPos) const 194 | { 195 | int32 const TipBoneLinkIndex = Chain.Num() - 1; 196 | 197 | ensure(Chain.IsValidIndex(TipBoneLinkIndex)); 198 | SafeCCDIKChainLink& CurrentLink = Chain[LinkIndex]; 199 | 200 | // update new tip pos 201 | FVector TipPos = Chain[TipBoneLinkIndex].Transform.GetLocation(); 202 | 203 | FTransform& CurrentLinkTransform = CurrentLink.Transform; 204 | FVector ToEnd = TipPos - CurrentLinkTransform.GetLocation(); 205 | FVector ToTarget = TargetPos - CurrentLinkTransform.GetLocation(); 206 | 207 | ToEnd.Normalize(); 208 | ToTarget.Normalize(); 209 | 210 | if (RotationLimitPerJoints.Num() > LinkIndex) 211 | { 212 | float RotationLimitPerJointInRadian = FMath::DegreesToRadians(RotationLimitPerJoints[LinkIndex]); 213 | float Angle = FMath::ClampAngle(FMath::Acos(FVector::DotProduct(ToEnd, ToTarget)), -RotationLimitPerJointInRadian, RotationLimitPerJointInRadian); 214 | bool bCanRotate = (FMath::Abs(Angle) > KINDA_SMALL_NUMBER) && (!bEnableRotationLimit || RotationLimitPerJointInRadian > CurrentLink.CurrentAngleDelta); 215 | if (bCanRotate) 216 | { 217 | // check rotation limit first, if fails, just abort 218 | if (bEnableRotationLimit) 219 | { 220 | if (RotationLimitPerJointInRadian < CurrentLink.CurrentAngleDelta + Angle) 221 | { 222 | Angle = RotationLimitPerJointInRadian - CurrentLink.CurrentAngleDelta; 223 | if (Angle <= KINDA_SMALL_NUMBER) 224 | { 225 | return false; 226 | } 227 | } 228 | 229 | CurrentLink.CurrentAngleDelta += Angle; 230 | } 231 | 232 | // continue with rotating toward to target 233 | FVector RotationAxis = FVector::CrossProduct(ToEnd, ToTarget); 234 | if (RotationAxis.SizeSquared() > 0.f) 235 | { 236 | RotationAxis.Normalize(); 237 | // Delta Rotation is the rotation to target 238 | FQuat DeltaRotation(RotationAxis, Angle); 239 | 240 | FQuat NewRotation = DeltaRotation * CurrentLinkTransform.GetRotation(); 241 | NewRotation.Normalize(); 242 | CurrentLinkTransform.SetRotation(NewRotation); 243 | 244 | // if I have parent, make sure to refresh local transform since my current transform has changed 245 | if (LinkIndex > 0) 246 | { 247 | SafeCCDIKChainLink const & Parent = Chain[LinkIndex - 1]; 248 | CurrentLink.LocalTransform = CurrentLinkTransform.GetRelativeTransform(Parent.Transform); 249 | CurrentLink.LocalTransform.NormalizeRotation(); 250 | } 251 | 252 | // now update all my children to have proper transform 253 | FTransform CurrentParentTransform = CurrentLinkTransform; 254 | 255 | // now update all chain 256 | for (int32 ChildLinkIndex = LinkIndex + 1; ChildLinkIndex <= TipBoneLinkIndex; ++ChildLinkIndex) 257 | { 258 | SafeCCDIKChainLink& ChildIterLink = Chain[ChildLinkIndex]; 259 | FCompactPoseBoneIndex ChildBoneIndex = ChildIterLink.BoneIndex; 260 | const FTransform LocalTransform = ChildIterLink.LocalTransform; 261 | ChildIterLink.Transform = LocalTransform * CurrentParentTransform; 262 | ChildIterLink.Transform.NormalizeRotation(); 263 | CurrentParentTransform = ChildIterLink.Transform; 264 | } 265 | 266 | return true; 267 | } 268 | } 269 | } 270 | 271 | return false; 272 | } 273 | 274 | bool FAnimNode_SafeCCDIK::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) 275 | { 276 | if (EffectorLocationSpace == BCS_ParentBoneSpace || EffectorLocationSpace == BCS_BoneSpace) 277 | { 278 | if (!EffectorTarget.IsValidToEvaluate(RequiredBones)) 279 | { 280 | return false; 281 | } 282 | } 283 | 284 | // Allow evaluation if all parameters are initialized and TipBone is child of RootBone 285 | return 286 | ( 287 | TipBone.IsValidToEvaluate(RequiredBones) 288 | && RootBone.IsValidToEvaluate(RequiredBones) 289 | && Precision > 0 290 | && RequiredBones.BoneIsChildOf(TipBone.BoneIndex, RootBone.BoneIndex) 291 | ); 292 | } 293 | 294 | #if WITH_EDITOR 295 | void FAnimNode_SafeCCDIK::ResizeRotationLimitPerJoints(int32 NewSize) 296 | { 297 | if (NewSize == 0) 298 | { 299 | RotationLimitPerJoints.Reset(); 300 | } 301 | else if (RotationLimitPerJoints.Num() != NewSize) 302 | { 303 | int32 StartIndex = RotationLimitPerJoints.Num(); 304 | RotationLimitPerJoints.SetNum(NewSize); 305 | for (int32 Index = StartIndex; Index < RotationLimitPerJoints.Num(); ++Index) 306 | { 307 | RotationLimitPerJoints[Index] = 30.f; 308 | } 309 | } 310 | } 311 | #endif 312 | 313 | void FAnimNode_SafeCCDIK::InitializeBoneReferences(const FBoneContainer& RequiredBones) 314 | { 315 | TipBone.Initialize(RequiredBones); 316 | RootBone.Initialize(RequiredBones); 317 | EffectorTarget.InitializeBoneReferences(RequiredBones); 318 | } 319 | 320 | void FAnimNode_SafeCCDIK::GatherDebugData(FNodeDebugData& DebugData) 321 | { 322 | FString DebugLine = DebugData.GetNodeName(this); 323 | 324 | DebugData.AddDebugItem(DebugLine); 325 | ComponentPose.GatherDebugData(DebugData); 326 | } 327 | 328 | PRAGMA_ENABLE_OPTIMIZATION -------------------------------------------------------------------------------- /Source/Nobunanim/Public/ProceduralAnimAsset.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Curves/CurveFloat.h" 10 | 11 | #include "ProceduralAnimAsset.generated.h" 12 | 13 | class UCurveFloat; 14 | class UCurveVector; 15 | 16 | USTRUCT(BlueprintType) 17 | struct FProceduralAnimData_CollisionAdjustment 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | /** Should this effector compute IK collisions? */ 23 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 24 | bool bComputeCollision; 25 | 26 | /** Collision channel to trace. */ 27 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 28 | TEnumAsByte TraceChannel = ECollisionChannel::ECC_Visibility; 29 | 30 | /** The name of the socket or bone to use as origin of the sweep. "None" will result to the use of the effector location. */ 31 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 32 | FName OriginCollisionSocketName; 33 | 34 | /** Raycast direction. If value == FVector::ZeroVector, take the velocity of the effector as direction. */ 35 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 36 | FVector RayDirection = FVector(0.f, 0.f, 0.f); 37 | }; 38 | 39 | USTRUCT(BlueprintType) 40 | struct FProceduralAnimData_Adjustment 41 | { 42 | GENERATED_BODY() 43 | 44 | public: 45 | /** Velocity factor to apply on the normalized velocity. */ 46 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 47 | FVector VelocityFactor = FVector(1.f, 1.f, 1.f); 48 | 49 | /** May use a simulated velocity? */ 50 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 51 | bool bUseSimulatedVelocity = false; 52 | 53 | /** The velocity to use if @bUseSimulatedVelocity is set to true. */ 54 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 55 | FVector SimulatedVelocity = FVector::ZeroVector; 56 | 57 | /** May the swing be adjust if too far from ideal effector. */ 58 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 59 | bool bForceSwingIfTooFar = true; 60 | 61 | /** Factor to calibrate the adjustement per axis. An axis value == 0 means no correction from this axis. */ 62 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 63 | FVector FactorPerAxis = FVector(1.f,1.f,0.f); 64 | 65 | /** Distance treshold to adjust (if enabled) if too far from ideal effector. */ 66 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 67 | float DistanceTreshold = 10.f; 68 | 69 | /** Collision adjustment data. */ 70 | //UPROPERTY(EditAnywhere, BlueprintReadWrite) 71 | //FProceduralAnimData_CollisionAdjustment CollisionData; 72 | }; 73 | 74 | USTRUCT(BlueprintType) 75 | struct FProceduralAnimData_AdditiveMovement 76 | { 77 | GENERATED_BODY() 78 | 79 | public: 80 | /** Should this effector be evaluated? Usefull for non destructive operation. */ 81 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "1")) 82 | bool bEvaluate = false; 83 | 84 | /** Lerp speed of the additive. If value <= 0, then no lerp. */ 85 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "2")) 86 | float LerpSpeed = -1.f; 87 | 88 | /** Additive curve. */ 89 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "3")) 90 | UCurveVector* AdditiveCurve; 91 | 92 | /** Scale of the additive curve. */ 93 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "4")) 94 | float Scale = 1.f; 95 | 96 | /** Factor to apply on additive curve. */ 97 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "5")) 98 | FVector Factor = FVector(1.f, 1.f, 1.f); 99 | }; 100 | 101 | USTRUCT(BlueprintType) 102 | struct FProceduralAnimData_AdditiveTranslation : public FProceduralAnimData_AdditiveMovement 103 | { 104 | GENERATED_BODY() 105 | 106 | public: 107 | /** Additional global offset. */ 108 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "6")) 109 | FVector AdditiveOffset = FVector::ZeroVector; 110 | 111 | /** Should the additive curve and offset be oriented in the direction of the speed or the front of the actor? */ 112 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "7")) 113 | bool bOrientToVelocity = true; 114 | 115 | /** Transform space to compute additive data. */ 116 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "8")) 117 | TEnumAsByte TransformSpace = ERelativeTransformSpace::RTS_World; 118 | }; 119 | 120 | USTRUCT(BlueprintType) 121 | struct FProceduralAnimData_AdditiveRotation : public FProceduralAnimData_AdditiveMovement 122 | { 123 | GENERATED_BODY() 124 | 125 | public: 126 | /** Additional global offset. */ 127 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "6")) 128 | FRotator AdditiveOffset = FRotator::ZeroRotator; 129 | 130 | ///** Should the additive curve be scaled in the direction of the velocity? */ 131 | //UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "7")) 132 | //bool bScaleByVelocity = false; 133 | 134 | /** Should the additive data be oriented to direction in local space? */ 135 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayPriority = "8")) 136 | bool bOrientToDirection = false; 137 | }; 138 | 139 | USTRUCT(BlueprintType) 140 | struct FProceduralAnimData_SwingAcceleration 141 | { 142 | GENERATED_BODY() 143 | 144 | public: 145 | /** Describe the acceleration of the swing until it reach his destination. A nullptr value will lead to a linear acceleration. 146 | * Think about it as: 147 | * - if currentCurveValue == 0, then the location of the effector is the origin. 148 | * - if currentCurveValue == 1, then the location of the effector is the ideal effector location (aka Socket location). 149 | * So if you want that the effector to follow closely to the ideal effector (i.e. when you want only additive rotation), 150 | * you can create a constant curve (t(0)=1, t(1)=1) and play with the lerp speed. */ 151 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 152 | FRuntimeFloatCurve AccelerationCurveX; 153 | /** Describe the acceleration of the swing until it reach his destination. A nullptr value will lead to a linear acceleration. 154 | * Think about it as: 155 | * - if currentCurveValue == 0, then the location of the effector is the origin. 156 | * - if currentCurveValue == 1, then the location of the effector is the ideal effector location (aka Socket location). 157 | * So if you want that the effector to follow closely to the ideal effector (i.e. when you want only additive rotation), 158 | * you can create a constant curve (t(0)=1, t(1)=1) and play with the lerp speed. */ 159 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 160 | FRuntimeFloatCurve AccelerationCurveY; 161 | /** Describe the acceleration of the swing until it reach his destination. A nullptr value will lead to a linear acceleration. 162 | * Think about it as: 163 | * - if currentCurveValue == 0, then the location of the effector is the origin. 164 | * - if currentCurveValue == 1, then the location of the effector is the ideal effector location (aka Socket location). 165 | * So if you want that the effector to follow closely to the ideal effector (i.e. when you want only additive rotation), 166 | * you can create a constant curve (t(0)=1, t(1)=1) and play with the lerp speed. */ 167 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 168 | FRuntimeFloatCurve AccelerationCurveZ; 169 | }; 170 | 171 | USTRUCT(BlueprintType) 172 | struct FProceduralAnimData_Movement 173 | { 174 | GENERATED_BODY() 175 | 176 | public: 177 | /** Describe the acceleration of the swing until it reach his destination. A nullptr value will lead to a linear acceleration. 178 | * Think about it as: 179 | * - if currentCurveValue == 0, then the location of the effector is the origin. 180 | * - if currentCurveValue == 1, then the location of the effector is the ideal effector location (aka Socket location). 181 | * So if you want that the effector to follow closely to the ideal effector (i.e. when you want only additive rotation), 182 | * you can create a constant curve (t(0)=1, t(1)=1) and play with the lerp speed. */ 183 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 184 | FRuntimeFloatCurve AccelerationCurve; 185 | 186 | /** Describe the acceleration of the swing until it reach his destination. */ 187 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 188 | FProceduralAnimData_SwingAcceleration AccelerationData; 189 | 190 | /** Lerp speed of the effector moving to it's ideal location. If value <= 0, then no there is no lerp and it's instantaneous. */ 191 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 192 | float LerpSpeed = -1.f; 193 | 194 | /** Additive data that will be added to the swing path. */ 195 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 196 | FProceduralAnimData_AdditiveTranslation AdditiveTranslationData; 197 | 198 | /** Additive rotation data that will be apply on the swing. */ 199 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 200 | FProceduralAnimData_AdditiveRotation AdditiveRotationData; 201 | }; 202 | 203 | USTRUCT(BlueprintType) 204 | struct FProceduralAnimData_Time 205 | { 206 | GENERATED_BODY() 207 | 208 | public: 209 | /** The name of the effector to link to. If valid, this effector will swing at the same time as the parent. Begin swing and End swing will be considered as offset (i.e. EndSwing = 0.1 will end at parent EndSwing + 0.1)*/ 210 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 211 | FName ParentEffector; 212 | 213 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 214 | bool bBindToParentBeginSwing = false; 215 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 216 | bool bBindToParentEndSwing = false; 217 | 218 | 219 | /** Begin of the swing in absolute time (0-1). If have a parent, this value will be added to the parent one. 220 | An effector beeing in 'Swing' describe the moment where it is active, else it is known as being in 'Stance'. */ 221 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 222 | float BeginSwing = 0.f; 223 | /** End of the swing in absolute time (0-1). If have a parent, this value will be added to the parent one. 224 | An effector beeing in 'Swing' describe the moment where it is active, else it is known as being in 'Stance'. */ 225 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 226 | float EndSwing = 1.f; 227 | }; 228 | 229 | USTRUCT(BlueprintType) 230 | struct FProceduralAnimData 231 | { 232 | GENERATED_BODY() 233 | 234 | public: 235 | /** Should this effector be evaluated? Usefull for non destructive operation. */ 236 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 237 | bool bEvaluate = true; 238 | 239 | /** Procedural animation time infos. */ 240 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite) 241 | FProceduralAnimData_Time TimeData; 242 | 243 | /** Procedural animation movement infos. */ 244 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite) 245 | FProceduralAnimData_Movement MovementData; 246 | 247 | /** Procedural animation adjustment infos. */ 248 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite) 249 | FProceduralAnimData_Adjustment AdjustmentData; 250 | }; 251 | 252 | UCLASS(BlueprintType) 253 | class NOBUNANIM_API UProceduralAnimAsset : public UPrimaryDataAsset 254 | { 255 | GENERATED_BODY() 256 | 257 | public: 258 | /** Procedural anim data per effector. */ 259 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite) 260 | TMap Effectors; 261 | 262 | /** Weight of this procedural animation. 0 means this animation will be overrided by any animation with greater weight. */ 263 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1", SliderMin = "0", SliderMax = "1")) 264 | float Weight = 0.0f; 265 | /** Animation frame number. */ 266 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0")) 267 | int AnimationFrameCount = 60; 268 | /** Animation refresh rate (or target FPS). */ 269 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "15", ClampMax = "60", SliderMin = "15", SliderMax = "60")) 270 | int AnimationFrameRate = 60; 271 | 272 | /** AnimationPlayRate. */ 273 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.01", ClampMax = "5", SliderMin = "0.01", SliderMax = "5")) 274 | float PlayRate = 1.0f; 275 | 276 | /** Events to trigger. Key is the name of the event, Value is the time to trigger in absolute time (between 0 and 1). */ 277 | UPROPERTY(Category = "[NOBUNANIM]|Procedural Anim Data|Events", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0.01", ClampMax = "5", SliderMin = "0.01", SliderMax = "5")) 278 | TMap EventsToTrigger; 279 | 280 | public: 281 | /** 282 | */ 283 | /** */ 284 | UFUNCTION(Category = "[NOBUNANIM]|Procedural Anim Data", BlueprintCallable) 285 | float GetFrameRatio() const; 286 | }; -------------------------------------------------------------------------------- /Source/Nobunanim/Private/ProceduralAnimator.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralAnimator.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | //#include 13 | 14 | #define FRAME_PER_SECOND 60.f 15 | 16 | #define CHECK_INIT() if (!bIsInitialized) { DEBUG_LOG(Warning, "Trying to call a method from a UProceduralAnimator despite it isn't initialized"); return; } 17 | #define ORIENT_TO_VEL(Input) adjData.VelocityFactor * (adjData.bUseSimulatedVelocity ? adjData.SimulatedVelocity : lastVelocity).Rotation().RotateVector(Input) 18 | 19 | void UProceduralAnimator::Initialize(USkeletalMeshComponent* _Mesh/*, ULocomotionComponent* _Locomotion*/) 20 | { 21 | if (_Mesh) 22 | { 23 | world = _Mesh->GetWorld(); 24 | mesh = _Mesh; 25 | 26 | bIsInitialized = world->IsGameWorld(); 27 | 28 | //locomotion = _Locomotion; 29 | } 30 | } 31 | 32 | void UProceduralAnimator::Add(UProceduralAnimAsset* _Anim, int _FrameDuration, bool bCached) 33 | { 34 | CHECK_INIT() 35 | 36 | if (_Anim == nullptr) 37 | { 38 | DEBUG_LOG(Warning, "Trying to add invalid procedural anim asset."); 39 | return; 40 | } 41 | 42 | FName animName = _Anim->GetFName(); 43 | 44 | if (!currentAnimSet.Contains(animName)) 45 | { 46 | currentAnimSet.Add(animName, _Anim); 47 | timeBuffers.Add(animName, 0.f); 48 | refreshRateBuffers.Add(animName, 0.f); 49 | 50 | // Extract each effectors from the procedural anim. 51 | for (TMap::TConstIterator it{ _Anim->Effectors.CreateConstIterator() }; it; ++it) 52 | { 53 | if (!effectors.Contains(it->Key)) 54 | { 55 | effectors.Add(it->Key); 56 | } 57 | } 58 | 59 | UpdateGlobalWeight(); 60 | } 61 | if (_FrameDuration > 0) 62 | { 63 | FTimerDelegate timerDelegate = FTimerDelegate::CreateUObject(this, &UProceduralAnimator::RemoveFromName, animName); 64 | float rate { _FrameDuration / FRAME_PER_SECOND }; 65 | 66 | if (!timerHandles.Contains(animName)) 67 | { 68 | timerHandles.Add(animName); 69 | } 70 | 71 | world->GetTimerManager().SetTimer(timerHandles[animName], timerDelegate, rate, false, rate); 72 | } 73 | else 74 | { 75 | // if infinite duration, try to remove existing timer. 76 | if (timerHandles.Contains(animName)) 77 | { 78 | world->GetTimerManager().ClearTimer(timerHandles[animName]); 79 | } 80 | } 81 | 82 | } 83 | 84 | void UProceduralAnimator::Remove(UProceduralAnimAsset* _Anim) 85 | { 86 | RemoveFromName(_Anim->GetFName()); 87 | } 88 | 89 | void UProceduralAnimator::RemoveFromName(FName _Key) 90 | { 91 | CHECK_INIT() 92 | 93 | if (!currentAnimSet.Contains(_Key)) 94 | { 95 | return; 96 | } 97 | 98 | if (timerHandles.Contains(_Key)) 99 | { 100 | world->GetTimerManager().ClearTimer(timerHandles[_Key]); 101 | timerHandles.Remove(_Key); 102 | } 103 | 104 | currentAnimSet.Remove(_Key); 105 | timeBuffers.Remove(_Key); 106 | refreshRateBuffers.Remove(_Key); 107 | UpdateGlobalWeight(); 108 | } 109 | 110 | void UProceduralAnimator::SetActive(bool _bEnable) 111 | { 112 | CHECK_INIT() 113 | 114 | if (!world || !world->IsGameWorld()) 115 | { 116 | return; 117 | } 118 | 119 | if (_bEnable /*&& bEvaluationActive*/) 120 | { 121 | bEvaluationActive = true; 122 | float delay = 1.f / (float)UNobunanimSettings::GetLODSetting(currentLOD = mesh->PredictedLODLevel).TargetFPS; 123 | 124 | // Set timer will auto-clear if needed. 125 | world->GetTimerManager().SetTimer(updateTimerHandle, this, &UProceduralAnimator::Evaluate_Internal, delay, true, false); 126 | 127 | Execute_SetProceduralGaitEnable(this, true); 128 | } 129 | else if (!_bEnable /*&& bEvaluationActive*/) 130 | { 131 | bEvaluationActive = false; 132 | world->GetTimerManager().ClearTimer(updateTimerHandle); 133 | 134 | Execute_SetProceduralGaitEnable(this, false); 135 | } 136 | } 137 | 138 | 139 | void UProceduralAnimator::Evaluate_Internal() 140 | { 141 | CHECK_INIT() 142 | 143 | if (!world || !world->IsGameWorld()) 144 | { 145 | return; 146 | } 147 | 148 | // Update effectors. 149 | UpdateEffectors(); 150 | 151 | // Update deltatime. 152 | { 153 | deltaTime = world->TimeSince(lastTime); 154 | lastTime = world->TimeSeconds; 155 | } 156 | 157 | // Update last velocity. 158 | { 159 | FVector currentVelocity = mesh->GetOwner()->GetVelocity(); 160 | if (bConserveVelocityIfZero && currentVelocity.SizeSquared() != 0.f) 161 | { 162 | lastVelocity = currentVelocity; 163 | lastVelocity.Normalize(); 164 | } 165 | 166 | //if (locomotion != nullptr) 167 | //{ 168 | // direction = locomotion->GetDirection() / 180.f * PI; 169 | //} 170 | } 171 | 172 | // Evaluate anim set. 173 | EvaluateAnimSet(); 174 | 175 | updateCount++; 176 | } 177 | 178 | void UProceduralAnimator::EvaluateAnimSet() 179 | { 180 | CHECK_INIT() 181 | 182 | for (TMap::TConstIterator it{ currentAnimSet.CreateConstIterator() }; it; ++it) 183 | { 184 | float weight { it->Value->Weight * globalWeight }; 185 | 186 | // Ignore animation with 0 alpha. 187 | if (weight == 0 && currentAnimSet.Num() > 1) 188 | { 189 | continue; 190 | } 191 | 192 | float buffToAdd = deltaTime * it->Value->GetFrameRatio() * it->Value->PlayRate; 193 | // Update time buffer. 194 | timeBuffers[it->Key] += buffToAdd; 195 | 196 | // Modulo to 1. 197 | float currentTime = FMath::Fmod(timeBuffers[it->Key], 1.f); 198 | 199 | 200 | for (TMap::TConstIterator ite{ it->Value->Effectors.CreateConstIterator() }; ite; ++ite) 201 | { 202 | if (effectors.Contains(ite->Key)) 203 | { 204 | EvaluateEffector(it->Value, ite->Value, ite->Key, currentTime); 205 | } 206 | } 207 | 208 | for (TMap::TConstIterator ite{ it->Value->EventsToTrigger.CreateConstIterator() }; ite; ++ite) 209 | { 210 | if (!PAAEventRaised.Contains(it->Key)) 211 | { 212 | PAAEventRaised.Add(it->Key, TArray()); 213 | PAAEventRaised[it->Key].Add(ite->Key); 214 | OnProceduralAnimAssetEvent.Broadcast(ite->Key); 215 | continue; 216 | } 217 | else if( (currentTime >= ite->Value || (ite->Value == 1.f && currentTime + buffToAdd >= 1.f)) 218 | && !PAAEventRaised[it->Key].Contains(ite->Key)) 219 | { 220 | PAAEventRaised[it->Key].Add(ite->Key); 221 | OnProceduralAnimAssetEvent.Broadcast(ite->Key); 222 | } 223 | } 224 | 225 | // @WIP: If current PAA end 226 | if (PAAEventRaised.Contains(it->Key) && currentTime + buffToAdd >= 1.f) 227 | { 228 | PAAEventRaised[it->Key].Empty(); 229 | PAAEventRaised.Remove(it->Key); 230 | } 231 | } 232 | } 233 | 234 | 235 | void UProceduralAnimator::UpdateEffectors() 236 | { 237 | for (TMap::TIterator it { effectors.CreateIterator() }; it; ++it) 238 | { 239 | // Get world space ideal effector location. 240 | FVector EffectorLocation = mesh->GetSocketLocation(it->Key); 241 | it->Value.IdLocation = EffectorLocation; 242 | } 243 | } 244 | 245 | void UProceduralAnimator::EvaluateEffector(const UProceduralAnimAsset* _AnimAsset, const FProceduralAnimData& _AnimData, const FName& _EffectorName, float _CurrentTime) 246 | { 247 | CHECK_INIT() 248 | 249 | if (!_AnimData.bEvaluate) 250 | { 251 | return; 252 | } 253 | 254 | // No need to check. 255 | FEffectorAnimData& effector = effectors[_EffectorName]; 256 | 257 | //float beginSwing, endSwing; 258 | FName parentName = _AnimData.TimeData.ParentEffector; 259 | if (!parentName.IsNone() && effectors.Contains(parentName)) 260 | { 261 | effector.currentBeginSwing = _AnimData.TimeData.bBindToParentBeginSwing ? effectors[parentName].currentBeginSwing : _AnimData.TimeData.BeginSwing; 262 | if (_AnimData.TimeData.bBindToParentEndSwing) 263 | { 264 | effector.currentEndSwing = effectors[parentName].currentEndSwing; 265 | } 266 | else 267 | { 268 | effector.currentEndSwing = effector.currentBeginSwing + _AnimData.TimeData.EndSwing; 269 | effector.currentEndSwing = effector.currentEndSwing > 1.f ? effector.currentEndSwing - 1.f : effector.currentEndSwing; 270 | } 271 | } 272 | else if (!effector.bForceSwing) 273 | { 274 | effector.currentBeginSwing = _AnimData.TimeData.BeginSwing; 275 | effector.currentEndSwing = _AnimData.TimeData.EndSwing; 276 | } 277 | 278 | float minRange, maxRange; 279 | 280 | bool bInRange = IsInSwingRange(_CurrentTime, effector.currentBeginSwing, effector.currentEndSwing, minRange, maxRange); 281 | if (bInRange) 282 | { 283 | if (!effector.bStartSwing) 284 | { 285 | effector.CurrentDestination = effector.CurrentLocation; 286 | effector.bStartSwing = true; 287 | } 288 | else if (effector.bForceSwing) 289 | { 290 | effector.CurrentDestination = effector.CurrentLocation; 291 | } 292 | 293 | float currentCurvePosition = FMath::GetMappedRangeValueClamped(FVector2D(minRange, maxRange), FVector2D(0.f, 1.f), _CurrentTime); 294 | 295 | // Per axis acceleration. 296 | const FProceduralAnimData_SwingAcceleration& accelData = _AnimData.MovementData.AccelerationData; 297 | effector.CurrentLocation.X = FMath::Lerp(effector.CurrentDestination.X, effector.IdLocation.X, 298 | accelData.AccelerationCurveX.GetRichCurveConst() ? 299 | accelData.AccelerationCurveX.GetRichCurveConst()->Eval(currentCurvePosition) : currentCurvePosition); 300 | effector.CurrentLocation.Y = FMath::Lerp(effector.CurrentDestination.Y, effector.IdLocation.Y, 301 | accelData.AccelerationCurveY.GetRichCurveConst() ? 302 | accelData.AccelerationCurveY.GetRichCurveConst()->Eval(currentCurvePosition) : currentCurvePosition); 303 | effector.CurrentLocation.Z = FMath::Lerp(effector.CurrentDestination.Z, effector.IdLocation.Z, 304 | accelData.AccelerationCurveZ.GetRichCurveConst() ? 305 | accelData.AccelerationCurveZ.GetRichCurveConst()->Eval(currentCurvePosition) : currentCurvePosition); 306 | 307 | // TO DO: Store the velocity one time per swing! 308 | // Store the current time per effector and each time 309 | 310 | // Additive rotation data. 311 | if (_AnimData.MovementData.AdditiveRotationData.bEvaluate) 312 | { 313 | const FProceduralAnimData_AdditiveRotation& rotationData = _AnimData.MovementData.AdditiveRotationData; 314 | FVector currentCurveValue = rotationData.Scale * rotationData.Factor * (rotationData.AdditiveCurve ? 315 | rotationData.AdditiveCurve->GetVectorValue(currentCurvePosition) : FVector::ZeroVector); 316 | FRotator additive = _AnimData.MovementData.AdditiveRotationData.AdditiveOffset; 317 | 318 | FRotator curveVal = FRotator(currentCurveValue.Y, currentCurveValue.Z, currentCurveValue.X); 319 | 320 | if (rotationData.bOrientToDirection) 321 | { 322 | FVector currentVel = (_AnimData.AdjustmentData.bUseSimulatedVelocity ? _AnimData.AdjustmentData.SimulatedVelocity : lastVelocity) * _AnimData.AdjustmentData.VelocityFactor; 323 | FVector currentOrientation = FVector(FMath::Cos(direction), FMath::Sin(direction), 0) * currentVel.Size2D(); 324 | 325 | if (curveVal != FRotator::ZeroRotator) 326 | { 327 | curveVal.Roll *= currentOrientation.X; 328 | curveVal.Pitch *= currentOrientation.Y; 329 | } 330 | if (additive != FRotator::ZeroRotator) 331 | { 332 | additive.Roll *= currentOrientation.X; 333 | additive.Pitch *= currentOrientation.Y; 334 | } 335 | } 336 | /*else if (rotationData.bScaleByVelocity) 337 | { 338 | FVector currentVel = (_AnimData.AdjustmentData.bUseSimulatedVelocity ? _AnimData.AdjustmentData.SimulatedVelocity : lastVelocity) * _AnimData.AdjustmentData.VelocityFactor; 339 | 340 | curveVal.Roll *= currentVel.X; 341 | curveVal.Pitch *= currentVel.Y; 342 | curveVal.Yaw *= currentVel.Z; 343 | 344 | additive.Roll *= currentVel.X; 345 | additive.Pitch *= currentVel.Y; 346 | additive.Yaw *= currentVel.Z; 347 | }*/ 348 | 349 | FRotator finalRot = curveVal + additive; 350 | Execute_UpdateEffectorRotation(this, _EffectorName, finalRot, rotationData.LerpSpeed/* * (1 / _AnimAsset->GetFrameRatio())*/); 351 | } 352 | 353 | // Additive translation data.z 354 | if (_AnimData.MovementData.AdditiveTranslationData.bEvaluate) 355 | { 356 | const FProceduralAnimData_AdditiveTranslation& transData = _AnimData.MovementData.AdditiveTranslationData; 357 | 358 | FVector currentCurveValue = transData.Scale * transData.Factor * (transData.AdditiveCurve ? 359 | transData.AdditiveCurve->GetVectorValue(currentCurvePosition) : FVector(1, 1, 1)); 360 | 361 | FVector offset = transData.AdditiveOffset * transData.Factor; 362 | 363 | if (transData.bOrientToVelocity) 364 | { 365 | const FProceduralAnimData_Adjustment adjData = _AnimData.AdjustmentData; 366 | currentCurveValue = ORIENT_TO_VEL(currentCurveValue); 367 | offset = ORIENT_TO_VEL(offset); 368 | } 369 | else 370 | { 371 | currentCurveValue = mesh->GetComponentRotation().RotateVector(currentCurveValue); 372 | offset = mesh->GetComponentRotation().RotateVector(offset); 373 | } 374 | 375 | FVector finalTranslation = offset + currentCurveValue + (transData.TransformSpace == ERelativeTransformSpace::RTS_World ? 376 | effector.CurrentLocation 377 | : mesh->GetSocketTransform(_EffectorName, transData.TransformSpace.GetValue()).GetLocation()); 378 | 379 | Execute_UpdateEffectorTranslation(this, _EffectorName, finalTranslation, transData.LerpSpeed > 0, transData.LerpSpeed/** (1 / _AnimAsset->GetFrameRatio())*/); 380 | } 381 | else 382 | { 383 | Execute_UpdateEffectorTranslation(this, _EffectorName, effector.CurrentLocation, _AnimData.MovementData.LerpSpeed > 0, _AnimData.MovementData.LerpSpeed/** (1 / _AnimAsset->GetFrameRatio())*/); 384 | } 385 | } 386 | else 387 | { 388 | effector.bStartSwing = false; 389 | effector.bForceSwing = false; 390 | 391 | if (_AnimData.AdjustmentData.bForceSwingIfTooFar && effectorsLocation.Contains(_EffectorName)) 392 | { 393 | const FProceduralAnimData_Adjustment& adjustData = _AnimData.AdjustmentData; 394 | // Compute lenght. 395 | float distance = (adjustData.FactorPerAxis * (effectorsLocation[_EffectorName] - effector.IdLocation)).SizeSquared(); 396 | 397 | if (distance >= FMath::Square(adjustData.DistanceTreshold)) 398 | { 399 | // Test combine swing. 400 | effector.currentBeginSwing = _CurrentTime; 401 | effector.currentEndSwing = _AnimData.TimeData.EndSwing; 402 | 403 | // edge case: When just after the swing the effectors need to be adjusted. 404 | if (FMath::IsNearlyEqual(effector.currentBeginSwing, effector.currentEndSwing, swingTimeTreshold)) 405 | { 406 | effector.currentEndSwing = _AnimData.TimeData.BeginSwing; 407 | } 408 | 409 | effector.bForceSwing = true; 410 | } 411 | } 412 | 413 | } 414 | 415 | } 416 | 417 | void UProceduralAnimator::UpdateGlobalWeight() 418 | { 419 | globalWeight = 0.0f; 420 | for (TMap::TConstIterator it{ currentAnimSet.CreateConstIterator() }; it; ++it) 421 | { 422 | globalWeight += it->Value->Weight; 423 | } 424 | 425 | globalWeight = globalWeight < TNumericLimits().Min() ? 1.0f : 1.0f / globalWeight; 426 | } 427 | 428 | bool UProceduralAnimator::IsInSwingRange(float Value, float Min, float Max, float& OutRangeMin, float& OutRangeMax) 429 | { 430 | bool A = Value >= Min && Value <= Max; 431 | bool APrime = Value >= Max; 432 | 433 | // Gave me headache ._. 434 | OutRangeMin = A ? Min : (APrime ? Min : Min - 1.f); 435 | OutRangeMax = A ? Max : (APrime ? 1.f /*- Min + Min*/ + Max : Max); 436 | 437 | if (A) 438 | { 439 | return true; 440 | } 441 | else 442 | { 443 | bool B1 = Value <= Min && Value <= Max; 444 | bool B2 = Value >= Min && Value >= Max; 445 | bool B3 = B1 || B2; 446 | bool C = Min >= Max && B3; 447 | 448 | return C; 449 | } 450 | } 451 | 452 | 453 | void UProceduralAnimator::UpdateEffectorTranslation_Implementation(const FName& _Socket, FVector _Translation, bool _bLerp, float _LerpSpeed) 454 | { 455 | const FVector* vec = effectorsLocation.Find(_Socket); 456 | if (!vec) 457 | { 458 | effectorsLocation.Add(_Socket, _Translation); 459 | } 460 | else 461 | { 462 | effectorsLocation[_Socket] = _bLerp ? FMath::VInterpTo(*vec, _Translation, FMath::Clamp(deltaTime, 0.f, 1.f), _LerpSpeed) : _Translation; 463 | } 464 | } 465 | 466 | void UProceduralAnimator::UpdateEffectorRotation_Implementation(const FName& _Socket, FRotator _Rotation, float _LerpSpeed) 467 | { 468 | const FRotator* rot = effectorsRotation.Find(_Socket); 469 | if (!rot) 470 | { 471 | effectorsRotation.Add(_Socket, _Rotation); 472 | } 473 | else 474 | { 475 | if (_LerpSpeed <= 0) 476 | { 477 | effectorsRotation[_Socket] = _Rotation; 478 | } 479 | 480 | effectorsRotation[_Socket] = FMath::RInterpTo(*rot, _Rotation, FMath::Clamp(deltaTime, 0.f, 1.f), _LerpSpeed); 481 | } 482 | } 483 | 484 | void UProceduralAnimator::SetProceduralGaitEnable_Implementation(bool _bEnable) 485 | { 486 | } 487 | -------------------------------------------------------------------------------- /Source/NobunanimEditor/Private/NobunanimBaseEditMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "NobunanimBaseEditMode.h" 4 | #include "EditorViewportClient.h" 5 | #include "IPersonaPreviewScene.h" 6 | #include "Animation/DebugSkelMeshComponent.h" 7 | #include "BoneControllers/AnimNode_SkeletalControlBase.h" 8 | #include "EngineUtils.h" 9 | #include "AnimGraphNode_SkeletalControlBase.h" 10 | #include "AssetEditorModeManager.h" 11 | 12 | #define LOCTEXT_NAMESPACE "AnimNodeEditMode" 13 | 14 | FNobunanimBaseEditMode::FNobunanimBaseEditMode() 15 | : AnimNode(nullptr) 16 | , RuntimeAnimNode(nullptr) 17 | , bManipulating(false) 18 | , bInTransaction(false) 19 | { 20 | // Disable grid drawing for this mode as the viewport handles this 21 | bDrawGrid = false; 22 | } 23 | 24 | bool FNobunanimBaseEditMode::GetCameraTarget(FSphere& OutTarget) const 25 | { 26 | FVector Location(GetWidgetLocation()); 27 | OutTarget.Center = Location; 28 | OutTarget.W = 50.0f; 29 | 30 | return true; 31 | } 32 | 33 | IPersonaPreviewScene& FNobunanimBaseEditMode::GetAnimPreviewScene() const 34 | { 35 | return *static_cast(static_cast(Owner)->GetPreviewScene()); 36 | } 37 | 38 | void FNobunanimBaseEditMode::GetOnScreenDebugInfo(TArray& OutDebugInfo) const 39 | { 40 | if (AnimNode != nullptr) 41 | { 42 | AnimNode->GetOnScreenDebugInfo(OutDebugInfo, RuntimeAnimNode, GetAnimPreviewScene().GetPreviewMeshComponent()); 43 | } 44 | } 45 | 46 | ECoordSystem FNobunanimBaseEditMode::GetWidgetCoordinateSystem() const 47 | { 48 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 49 | if (SkelControl != nullptr) 50 | { 51 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 52 | return (ECoordSystem)SkelControl->GetWidgetCoordinateSystem(GetAnimPreviewScene().GetPreviewMeshComponent()); 53 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 54 | } 55 | 56 | return ECoordSystem::COORD_None; 57 | } 58 | 59 | FWidget::EWidgetMode FNobunanimBaseEditMode::GetWidgetMode() const 60 | { 61 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 62 | if (SkelControl != nullptr) 63 | { 64 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 65 | return (FWidget::EWidgetMode)SkelControl->GetWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent()); 66 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 67 | } 68 | 69 | return FWidget::EWidgetMode::WM_None; 70 | } 71 | 72 | FWidget::EWidgetMode FNobunanimBaseEditMode::ChangeToNextWidgetMode(FWidget::EWidgetMode CurWidgetMode) 73 | { 74 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 75 | if (SkelControl != nullptr) 76 | { 77 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 78 | return (FWidget::EWidgetMode)SkelControl->ChangeToNextWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent(), CurWidgetMode); 79 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 80 | } 81 | 82 | return FWidget::EWidgetMode::WM_None; 83 | } 84 | 85 | bool FNobunanimBaseEditMode::SetWidgetMode(FWidget::EWidgetMode InWidgetMode) 86 | { 87 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 88 | if (SkelControl != nullptr) 89 | { 90 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 91 | return SkelControl->SetWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent(), InWidgetMode); 92 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 93 | } 94 | 95 | return false; 96 | } 97 | 98 | FName FNobunanimBaseEditMode::GetSelectedBone() const 99 | { 100 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 101 | if (SkelControl != nullptr) 102 | { 103 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 104 | return SkelControl->FindSelectedBone(); 105 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 106 | } 107 | 108 | return NAME_None; 109 | } 110 | 111 | void FNobunanimBaseEditMode::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimNode_Base* InRuntimeNode) 112 | { 113 | AnimNode = InEditorNode; 114 | RuntimeAnimNode = InRuntimeNode; 115 | 116 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 117 | if (SkelControl != nullptr) 118 | { 119 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 120 | SkelControl->MoveSelectActorLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 121 | SkelControl->CopyNodeDataTo(RuntimeAnimNode); 122 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 123 | } 124 | 125 | GetModeManager()->SetCoordSystem(GetWidgetCoordinateSystem()); 126 | GetModeManager()->SetWidgetMode(GetWidgetMode()); 127 | } 128 | 129 | void FNobunanimBaseEditMode::ExitMode() 130 | { 131 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 132 | if (SkelControl != nullptr) 133 | { 134 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 135 | SkelControl->DeselectActor(GetAnimPreviewScene().GetPreviewMeshComponent()); 136 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 137 | } 138 | 139 | AnimNode = nullptr; 140 | RuntimeAnimNode = nullptr; 141 | } 142 | 143 | void FNobunanimBaseEditMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) 144 | { 145 | if (AnimNode != nullptr) 146 | { 147 | AnimNode->Draw(PDI, GetAnimPreviewScene().GetPreviewMeshComponent()); 148 | } 149 | } 150 | 151 | void FNobunanimBaseEditMode::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) 152 | { 153 | if (AnimNode != nullptr) 154 | { 155 | AnimNode->DrawCanvas(*Viewport, *const_cast(View), *Canvas, GetAnimPreviewScene().GetPreviewMeshComponent()); 156 | } 157 | } 158 | 159 | bool FNobunanimBaseEditMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) 160 | { 161 | if (HitProxy != nullptr && HitProxy->IsA(HActor::StaticGetType())) 162 | { 163 | HActor* ActorHitProxy = static_cast(HitProxy); 164 | GetAnimPreviewScene().SetSelectedActor(ActorHitProxy->Actor); 165 | 166 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 167 | if (SkelControl != nullptr) 168 | { 169 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 170 | SkelControl->ProcessActorClick(ActorHitProxy); 171 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 172 | } 173 | return true; 174 | } 175 | 176 | return false; 177 | } 178 | 179 | FVector FNobunanimBaseEditMode::GetWidgetLocation() const 180 | { 181 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 182 | if (SkelControl != nullptr) 183 | { 184 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 185 | return SkelControl->GetWidgetLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 186 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 187 | } 188 | 189 | return FVector::ZeroVector; 190 | } 191 | 192 | bool FNobunanimBaseEditMode::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) 193 | { 194 | if (!bInTransaction) 195 | { 196 | GEditor->BeginTransaction(LOCTEXT("EditSkelControlNodeTransaction", "Edit Skeletal Control Node")); 197 | AnimNode->SetFlags(RF_Transactional); 198 | AnimNode->Modify(); 199 | bInTransaction = true; 200 | } 201 | 202 | bManipulating = true; 203 | 204 | return true; 205 | } 206 | 207 | bool FNobunanimBaseEditMode::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) 208 | { 209 | if (bManipulating) 210 | { 211 | bManipulating = false; 212 | } 213 | 214 | if (bInTransaction) 215 | { 216 | GEditor->EndTransaction(); 217 | bInTransaction = false; 218 | } 219 | 220 | return true; 221 | } 222 | 223 | bool FNobunanimBaseEditMode::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) 224 | { 225 | bool bHandled = false; 226 | 227 | // Handle switching modes - only allowed when not already manipulating 228 | if ((InEvent == IE_Pressed) && (InKey == EKeys::SpaceBar) && !bManipulating) 229 | { 230 | FWidget::EWidgetMode WidgetMode = (FWidget::EWidgetMode)ChangeToNextWidgetMode(GetModeManager()->GetWidgetMode()); 231 | GetModeManager()->SetWidgetMode(WidgetMode); 232 | if (WidgetMode == FWidget::WM_Scale) 233 | { 234 | GetModeManager()->SetCoordSystem(COORD_Local); 235 | } 236 | else 237 | { 238 | GetModeManager()->SetCoordSystem(COORD_World); 239 | } 240 | 241 | bHandled = true; 242 | InViewportClient->Invalidate(); 243 | } 244 | 245 | return bHandled; 246 | } 247 | 248 | bool FNobunanimBaseEditMode::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) 249 | { 250 | const EAxisList::Type CurrentAxis = InViewportClient->GetCurrentWidgetAxis(); 251 | const FWidget::EWidgetMode WidgetMode = InViewportClient->GetWidgetMode(); 252 | 253 | bool bHandled = false; 254 | 255 | UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene().GetPreviewMeshComponent(); 256 | 257 | if (bManipulating && CurrentAxis != EAxisList::None) 258 | { 259 | bHandled = true; 260 | 261 | const bool bDoRotation = WidgetMode == FWidget::WM_Rotate || WidgetMode == FWidget::WM_TranslateRotateZ; 262 | const bool bDoTranslation = WidgetMode == FWidget::WM_Translate || WidgetMode == FWidget::WM_TranslateRotateZ; 263 | const bool bDoScale = WidgetMode == FWidget::WM_Scale; 264 | 265 | if (bDoRotation) 266 | { 267 | DoRotation(InRot); 268 | } 269 | 270 | if (bDoTranslation) 271 | { 272 | DoTranslation(InDrag); 273 | } 274 | 275 | if (bDoScale) 276 | { 277 | DoScale(InScale); 278 | } 279 | 280 | InViewport->Invalidate(); 281 | } 282 | 283 | return bHandled; 284 | } 285 | 286 | bool FNobunanimBaseEditMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) 287 | { 288 | UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene().GetPreviewMeshComponent(); 289 | FName BoneName = GetSelectedBone(); 290 | int32 BoneIndex = PreviewMeshComponent->GetBoneIndex(BoneName); 291 | if (BoneIndex != INDEX_NONE) 292 | { 293 | FTransform BoneMatrix = PreviewMeshComponent->GetBoneTransform(BoneIndex); 294 | InMatrix = BoneMatrix.ToMatrixNoScale().RemoveTranslation(); 295 | return true; 296 | } 297 | 298 | return false; 299 | } 300 | 301 | bool FNobunanimBaseEditMode::GetCustomInputCoordinateSystem(FMatrix& InMatrix, void* InData) 302 | { 303 | return GetCustomDrawingCoordinateSystem(InMatrix, InData); 304 | } 305 | 306 | bool FNobunanimBaseEditMode::ShouldDrawWidget() const 307 | { 308 | return true; 309 | } 310 | 311 | void FNobunanimBaseEditMode::DoTranslation(FVector& InTranslation) 312 | { 313 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 314 | if (SkelControl != nullptr) 315 | { 316 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 317 | SkelControl->DoTranslation(GetAnimPreviewScene().GetPreviewMeshComponent(), InTranslation, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 318 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 319 | } 320 | } 321 | 322 | void FNobunanimBaseEditMode::DoRotation(FRotator& InRotation) 323 | { 324 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 325 | if (SkelControl != nullptr) 326 | { 327 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 328 | SkelControl->DoRotation(GetAnimPreviewScene().GetPreviewMeshComponent(), InRotation, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 329 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 330 | } 331 | } 332 | 333 | void FNobunanimBaseEditMode::DoScale(FVector& InScale) 334 | { 335 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 336 | if (SkelControl != nullptr) 337 | { 338 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 339 | SkelControl->DoScale(GetAnimPreviewScene().GetPreviewMeshComponent(), InScale, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 340 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 341 | } 342 | } 343 | 344 | void FNobunanimBaseEditMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) 345 | { 346 | IAnimNodeEditMode::Tick(ViewportClient, DeltaTime); 347 | 348 | // Keep actor location in sync with animation 349 | UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); 350 | if (SkelControl != nullptr) 351 | { 352 | PRAGMA_DISABLE_DEPRECATION_WARNINGS 353 | SkelControl->MoveSelectActorLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); 354 | PRAGMA_ENABLE_DEPRECATION_WARNINGS 355 | } 356 | } 357 | 358 | void FNobunanimBaseEditMode::ConvertToComponentSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InTransform, FTransform & OutCSTransform, int32 BoneIndex, EBoneControlSpace Space) 359 | { 360 | USkeleton* Skeleton = SkelComp->SkeletalMesh->Skeleton; 361 | 362 | switch (Space) 363 | { 364 | case BCS_WorldSpace: 365 | { 366 | OutCSTransform = InTransform; 367 | OutCSTransform.SetToRelativeTransform(SkelComp->GetComponentTransform()); 368 | } 369 | break; 370 | 371 | case BCS_ComponentSpace: 372 | { 373 | // Component Space, no change. 374 | OutCSTransform = InTransform; 375 | } 376 | break; 377 | 378 | case BCS_ParentBoneSpace: 379 | if (BoneIndex != INDEX_NONE) 380 | { 381 | const int32 ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); 382 | if (ParentIndex != INDEX_NONE) 383 | { 384 | const int32 MeshParentIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, ParentIndex); 385 | if (MeshParentIndex != INDEX_NONE) 386 | { 387 | const FTransform ParentTM = SkelComp->GetBoneTransform(MeshParentIndex); 388 | OutCSTransform = InTransform * ParentTM; 389 | } 390 | else 391 | { 392 | OutCSTransform = InTransform; 393 | } 394 | } 395 | } 396 | break; 397 | 398 | case BCS_BoneSpace: 399 | if (BoneIndex != INDEX_NONE) 400 | { 401 | const int32 MeshBoneIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, BoneIndex); 402 | if (MeshBoneIndex != INDEX_NONE) 403 | { 404 | const FTransform BoneTM = SkelComp->GetBoneTransform(MeshBoneIndex); 405 | OutCSTransform = InTransform * BoneTM; 406 | } 407 | else 408 | { 409 | OutCSTransform = InTransform; 410 | } 411 | } 412 | break; 413 | 414 | default: 415 | if (SkelComp->SkeletalMesh) 416 | { 417 | UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Mesh: %s"), (uint8)Space, *SkelComp->SkeletalMesh->GetFName().ToString()); 418 | } 419 | else 420 | { 421 | UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Skeleton: %s"), (uint8)Space, *Skeleton->GetFName().ToString()); 422 | } 423 | break; 424 | } 425 | } 426 | 427 | 428 | void FNobunanimBaseEditMode::ConvertToBoneSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InCSTransform, FTransform & OutBSTransform, int32 BoneIndex, EBoneControlSpace Space) 429 | { 430 | USkeleton* Skeleton = SkelComp->SkeletalMesh->Skeleton; 431 | 432 | switch (Space) 433 | { 434 | case BCS_WorldSpace: 435 | { 436 | OutBSTransform = InCSTransform * SkelComp->GetComponentTransform(); 437 | break; 438 | } 439 | 440 | case BCS_ComponentSpace: 441 | { 442 | // Component Space, no change. 443 | OutBSTransform = InCSTransform; 444 | break; 445 | } 446 | 447 | case BCS_ParentBoneSpace: 448 | { 449 | if (BoneIndex != INDEX_NONE) 450 | { 451 | const int32 ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); 452 | if (ParentIndex != INDEX_NONE) 453 | { 454 | const int32 MeshParentIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, ParentIndex); 455 | if (MeshParentIndex != INDEX_NONE) 456 | { 457 | const FTransform ParentTM = SkelComp->GetBoneTransform(MeshParentIndex); 458 | OutBSTransform = InCSTransform.GetRelativeTransform(ParentTM); 459 | } 460 | else 461 | { 462 | OutBSTransform = InCSTransform; 463 | } 464 | } 465 | } 466 | break; 467 | } 468 | 469 | case BCS_BoneSpace: 470 | { 471 | if (BoneIndex != INDEX_NONE) 472 | { 473 | const int32 MeshBoneIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, BoneIndex); 474 | if (MeshBoneIndex != INDEX_NONE) 475 | { 476 | FTransform BoneCSTransform = SkelComp->GetBoneTransform(MeshBoneIndex); 477 | OutBSTransform = InCSTransform.GetRelativeTransform(BoneCSTransform); 478 | } 479 | else 480 | { 481 | OutBSTransform = InCSTransform; 482 | } 483 | } 484 | break; 485 | } 486 | 487 | default: 488 | { 489 | UE_LOG(LogAnimation, Warning, TEXT("ConvertToBoneSpaceTransform: Unknown BoneSpace %d for Mesh: %s"), (int32)Space, *GetNameSafe(SkelComp->SkeletalMesh)); 490 | break; 491 | } 492 | } 493 | } 494 | 495 | FVector FNobunanimBaseEditMode::ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FBoneSocketTarget& InTarget, const EBoneControlSpace Space) 496 | { 497 | FVector OutVector = FVector::ZeroVector; 498 | 499 | if (MeshBases.GetPose().IsValid()) 500 | { 501 | const FCompactPoseBoneIndex BoneIndex = InTarget.GetCompactPoseBoneIndex(); 502 | 503 | switch (Space) 504 | { 505 | // World Space, no change in preview window 506 | case BCS_WorldSpace: 507 | case BCS_ComponentSpace: 508 | // Component Space, no change. 509 | OutVector = InCSVector; 510 | break; 511 | 512 | case BCS_ParentBoneSpace: 513 | { 514 | if (BoneIndex != INDEX_NONE) 515 | { 516 | const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); 517 | if (ParentIndex != INDEX_NONE) 518 | { 519 | const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); 520 | OutVector = ParentTM.InverseTransformVector(InCSVector); 521 | } 522 | } 523 | } 524 | break; 525 | 526 | case BCS_BoneSpace: 527 | { 528 | FTransform BoneTransform = InTarget.GetTargetTransform(FVector::ZeroVector, MeshBases, SkelComp->GetComponentToWorld()); 529 | OutVector = BoneTransform.InverseTransformVector(InCSVector); 530 | } 531 | break; 532 | } 533 | } 534 | 535 | return OutVector; 536 | } 537 | 538 | FVector FNobunanimBaseEditMode::ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space) 539 | { 540 | FVector OutVector = FVector::ZeroVector; 541 | 542 | if (MeshBases.GetPose().IsValid()) 543 | { 544 | const FMeshPoseBoneIndex MeshBoneIndex(SkelComp->GetBoneIndex(BoneName)); 545 | const FCompactPoseBoneIndex BoneIndex = MeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(MeshBoneIndex); 546 | 547 | switch (Space) 548 | { 549 | // World Space, no change in preview window 550 | case BCS_WorldSpace: 551 | case BCS_ComponentSpace: 552 | // Component Space, no change. 553 | OutVector = InCSVector; 554 | break; 555 | 556 | case BCS_ParentBoneSpace: 557 | { 558 | const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); 559 | if (ParentIndex != INDEX_NONE) 560 | { 561 | const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); 562 | OutVector = ParentTM.InverseTransformVector(InCSVector); 563 | } 564 | } 565 | break; 566 | 567 | case BCS_BoneSpace: 568 | { 569 | if (BoneIndex != INDEX_NONE) 570 | { 571 | const FTransform& BoneTM = MeshBases.GetComponentSpaceTransform(BoneIndex); 572 | OutVector = BoneTM.InverseTransformVector(InCSVector); 573 | } 574 | } 575 | break; 576 | } 577 | } 578 | 579 | return OutVector; 580 | } 581 | 582 | FQuat FNobunanimBaseEditMode::ConvertCSRotationToBoneSpace(const USkeletalMeshComponent* SkelComp, FRotator& InCSRotator, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space) 583 | { 584 | FQuat OutQuat = FQuat::Identity; 585 | 586 | if (MeshBases.GetPose().IsValid()) 587 | { 588 | const FMeshPoseBoneIndex MeshBoneIndex(SkelComp->GetBoneIndex(BoneName)); 589 | const FCompactPoseBoneIndex BoneIndex = MeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(MeshBoneIndex); 590 | 591 | FVector RotAxis; 592 | float RotAngle; 593 | InCSRotator.Quaternion().ToAxisAndAngle(RotAxis, RotAngle); 594 | 595 | switch (Space) 596 | { 597 | // World Space, no change in preview window 598 | case BCS_WorldSpace: 599 | case BCS_ComponentSpace: 600 | // Component Space, no change. 601 | OutQuat = InCSRotator.Quaternion(); 602 | break; 603 | 604 | case BCS_ParentBoneSpace: 605 | { 606 | const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); 607 | if (ParentIndex != INDEX_NONE) 608 | { 609 | const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); 610 | FTransform InverseParentTM = ParentTM.Inverse(); 611 | //Calculate the new delta rotation 612 | FVector4 BoneSpaceAxis = InverseParentTM.TransformVector(RotAxis); 613 | FQuat DeltaQuat(BoneSpaceAxis, RotAngle); 614 | DeltaQuat.Normalize(); 615 | OutQuat = DeltaQuat; 616 | } 617 | } 618 | break; 619 | 620 | case BCS_BoneSpace: 621 | { 622 | const FTransform& BoneTM = MeshBases.GetComponentSpaceTransform(BoneIndex); 623 | FTransform InverseBoneTM = BoneTM.Inverse(); 624 | FVector4 BoneSpaceAxis = InverseBoneTM.TransformVector(RotAxis); 625 | //Calculate the new delta rotation 626 | FQuat DeltaQuat(BoneSpaceAxis, RotAngle); 627 | DeltaQuat.Normalize(); 628 | OutQuat = DeltaQuat; 629 | } 630 | break; 631 | } 632 | } 633 | 634 | return OutQuat; 635 | } 636 | 637 | FVector FNobunanimBaseEditMode::ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FBoneSocketTarget& Target, const FVector& InLocation, const EBoneControlSpace Space) 638 | { 639 | FVector WidgetLoc = FVector::ZeroVector; 640 | 641 | switch (Space) 642 | { 643 | // GetComponentTransform() must be Identity in preview window so same as ComponentSpace 644 | case BCS_WorldSpace: 645 | case BCS_ComponentSpace: 646 | { 647 | // Component Space, no change. 648 | WidgetLoc = InLocation; 649 | } 650 | break; 651 | 652 | case BCS_ParentBoneSpace: 653 | { 654 | const FCompactPoseBoneIndex CompactBoneIndex = Target.GetCompactPoseBoneIndex(); 655 | 656 | if (CompactBoneIndex != INDEX_NONE) 657 | { 658 | if (ensure(InMeshBases.GetPose().IsValidIndex(CompactBoneIndex))) 659 | { 660 | const FCompactPoseBoneIndex CompactParentIndex = InMeshBases.GetPose().GetParentBoneIndex(CompactBoneIndex); 661 | if (CompactParentIndex != INDEX_NONE) 662 | { 663 | const FTransform& ParentTM = InMeshBases.GetComponentSpaceTransform(CompactParentIndex); 664 | WidgetLoc = ParentTM.TransformPosition(InLocation); 665 | } 666 | } 667 | else 668 | { 669 | UE_LOG(LogAnimation, Warning, TEXT("Using socket(%d), Socket name(%s), Bone name(%s)"), 670 | Target.bUseSocket, *Target.SocketReference.SocketName.ToString(), *Target.BoneReference.BoneName.ToString()); 671 | } 672 | } 673 | } 674 | break; 675 | 676 | case BCS_BoneSpace: 677 | { 678 | FTransform BoneTM = Target.GetTargetTransform(FVector::ZeroVector, InMeshBases, InSkelComp->GetComponentToWorld()); 679 | WidgetLoc = BoneTM.TransformPosition(InLocation); 680 | } 681 | break; 682 | } 683 | 684 | return WidgetLoc; 685 | } 686 | FVector FNobunanimBaseEditMode::ConvertWidgetLocation(const USkeletalMeshComponent* SkelComp, FCSPose& MeshBases, const FName& BoneName, const FVector& Location, const EBoneControlSpace Space) 687 | { 688 | FVector WidgetLoc = FVector::ZeroVector; 689 | 690 | auto GetCompactBoneIndex = [](const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FName& InBoneName) 691 | { 692 | if (InMeshBases.GetPose().IsValid()) 693 | { 694 | USkeleton* Skeleton = InSkelComp->SkeletalMesh->Skeleton; 695 | const int32 MeshBoneIndex = InSkelComp->GetBoneIndex(InBoneName); 696 | if (MeshBoneIndex != INDEX_NONE) 697 | { 698 | return InMeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(FMeshPoseBoneIndex(MeshBoneIndex)); 699 | } 700 | } 701 | 702 | return FCompactPoseBoneIndex(INDEX_NONE); 703 | }; 704 | 705 | switch (Space) 706 | { 707 | // GetComponentTransform() must be Identity in preview window so same as ComponentSpace 708 | case BCS_WorldSpace: 709 | case BCS_ComponentSpace: 710 | { 711 | // Component Space, no change. 712 | WidgetLoc = Location; 713 | } 714 | break; 715 | 716 | case BCS_ParentBoneSpace: 717 | { 718 | const FCompactPoseBoneIndex CompactBoneIndex = GetCompactBoneIndex(SkelComp, MeshBases, BoneName); 719 | if (CompactBoneIndex != INDEX_NONE) 720 | { 721 | const FCompactPoseBoneIndex CompactParentIndex = MeshBases.GetPose().GetParentBoneIndex(CompactBoneIndex); 722 | if (CompactParentIndex != INDEX_NONE) 723 | { 724 | const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(CompactParentIndex); 725 | WidgetLoc = ParentTM.TransformPosition(Location); 726 | } 727 | } 728 | } 729 | break; 730 | 731 | case BCS_BoneSpace: 732 | { 733 | const FCompactPoseBoneIndex CompactBoneIndex = GetCompactBoneIndex(SkelComp, MeshBases, BoneName); 734 | if (CompactBoneIndex != INDEX_NONE) 735 | { 736 | const FTransform& BoneTM = MeshBases.GetComponentSpaceTransform(CompactBoneIndex); 737 | WidgetLoc = BoneTM.TransformPosition(Location); 738 | } 739 | } 740 | break; 741 | } 742 | 743 | return WidgetLoc; 744 | } 745 | 746 | #undef LOCTEXT_NAMESPACE 747 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/ProceduralGaitControllerComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralGaitControllerComponent.h" 5 | 6 | #include "Nobunanim/Private/Nobunanim.h" 7 | #include "Nobunanim/Public/GaitDataAsset.h" 8 | #include "Nobunanim/Public/ProceduralGaitAnimInstance.h" 9 | #include "Nobunanim/Public/NobunanimSettings.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #define ORIENT_TO_VELOCITY(Input) LastVelocity.Rotation().RotateVector(Input) 20 | 21 | #define SPHERECAST_IK_CORRECTION_RADIUS 30.f 22 | 23 | 24 | // Sets default values for this component's properties 25 | UProceduralGaitControllerComponent::UProceduralGaitControllerComponent() 26 | { 27 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 28 | // off to improve performance if you don't need them. 29 | PrimaryComponentTick.bCanEverTick = true; 30 | } 31 | 32 | 33 | // Called when the game starts 34 | void UProceduralGaitControllerComponent::BeginPlay() 35 | { 36 | Super::BeginPlay(); 37 | 38 | // INIT REFERENCES 39 | { 40 | if (!AnimInstanceRef) 41 | { 42 | AActor* Owner = GetOwner(); 43 | if (Owner) 44 | { 45 | ACharacter* OwnerCharacter = Cast(Owner); 46 | OwnedMesh = OwnerCharacter->GetMesh(); 47 | 48 | if (OwnedMesh) 49 | { 50 | UAnimInstance* AnimInstance = OwnedMesh->GetAnimInstance(); 51 | 52 | if (AnimInstance) 53 | { 54 | AnimInstanceRef = Cast(AnimInstance); 55 | if (!AnimInstanceRef) 56 | { 57 | DEBUG_LOG_FORMAT(Warning, "Invalid anim instance own by actor %s.", *Owner->GetName()); 58 | } 59 | } 60 | else 61 | { 62 | AnimInstance = OwnedMesh->GetPostProcessInstance(); 63 | if (AnimInstance) 64 | { 65 | AnimInstanceRef = Cast(AnimInstance); 66 | if (!AnimInstanceRef) 67 | { 68 | DEBUG_LOG_FORMAT(Warning, "Invalid anim instance own by actor %s.", *Owner->GetName()); 69 | } 70 | } 71 | else 72 | { 73 | DEBUG_LOG_FORMAT(Warning, "No valid anim instance own by actor %s.", *Owner->GetName()); 74 | } 75 | } 76 | } 77 | else 78 | { 79 | SetComponentTickEnabled(false); 80 | } 81 | } 82 | } 83 | } 84 | 85 | { 86 | SetComponentTickEnabled(AnimInstanceRef); 87 | } 88 | } 89 | 90 | 91 | 92 | bool UProceduralGaitControllerComponent::TraceRay(UWorld* World, TArray& HitResults, FVector Origin, FVector Dest, TEnumAsByte TraceChannel, float SphereCastRadius) 93 | { 94 | const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 95 | 96 | // if correction Level0 then zero computation. 97 | if (LODSetting.CorrectionLevel == ENobunanimIKCorrectionLevel::IKL_Level0) 98 | { 99 | return false; 100 | } 101 | 102 | FCollisionQueryParams SweepParam(*GetOwner()->GetName(), LODSetting.bTraceOnComplex, GetOwner()); 103 | 104 | bool bFoundHit = World->LineTraceMultiByChannel 105 | ( 106 | HitResults, 107 | Origin, 108 | Dest, 109 | TraceChannel, 110 | SweepParam, 111 | FCollisionResponseParams::DefaultResponseParam 112 | ); 113 | 114 | #if WITH_EDITOR 115 | if (LODSetting.Debug.bShowCollisionCorrection) 116 | { 117 | DrawDebugDirectionalArrow(World, Origin, Dest, 5, LODSetting.Debug.IKTraceColor, false, LODSetting.Debug.IKTraceDuration, 0, 0.5f); 118 | } 119 | #endif 120 | 121 | if (LODSetting.CorrectionLevel == ENobunanimIKCorrectionLevel::IKL_Level1) 122 | { 123 | return bFoundHit; 124 | } 125 | 126 | if (!bFoundHit) 127 | { 128 | bFoundHit = World->SweepMultiByChannel 129 | ( 130 | HitResults, 131 | Origin, 132 | Dest, 133 | FQuat::Identity, 134 | TraceChannel, 135 | FCollisionShape::MakeSphere(SphereCastRadius), 136 | SweepParam, 137 | FCollisionResponseParams::DefaultResponseParam 138 | ); 139 | 140 | #if WITH_EDITOR 141 | if (LODSetting.Debug.bShowCollisionCorrection) 142 | { 143 | DrawDebugCapsule(World, (Dest + Origin) * 0.5f, ((Dest - Origin)).Size()* 0.5f, SphereCastRadius, FQuat((Origin - Dest).GetUnsafeNormal().Rotation()), LODSetting.Debug.LODColor, false, LODSetting.Debug.IKTraceDuration, 0, .5f); 144 | } 145 | #endif 146 | } 147 | 148 | return bFoundHit; 149 | } 150 | 151 | FHitResult& UProceduralGaitControllerComponent::GetBestHitResult(TArray& HitResults, FVector IdealLocation) 152 | { 153 | FHitResult& HitResult = HitResults[0]; 154 | 155 | float BestDistance = 9999999.f; 156 | for (int i = 0, n = HitResults.Num(); i < n; ++i) 157 | { 158 | FHitResult& CurrentHit = HitResults[i]; 159 | 160 | float Temp = FMath::Abs(((IdealLocation)-CurrentHit.ImpactPoint).SizeSquared()); 161 | if (Temp < BestDistance) 162 | { 163 | BestDistance = Temp; 164 | HitResult = CurrentHit; 165 | } 166 | } 167 | 168 | return HitResult; 169 | } 170 | 171 | // Called every frame 172 | void UProceduralGaitControllerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 173 | { 174 | NOBUNANIM_SCOPE_COUNTER(ProceduralGait_Tick); 175 | 176 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 177 | 178 | 179 | FVector NewCurrentLocation; 180 | FVector IdealEffectorLocation; 181 | FVector CurrentEffectorLocation; 182 | float Treshold = 0.f; 183 | bool bForceSwing = false; 184 | bool bAllEffectorBlendOutEnd = true; 185 | 186 | UWorld* World = GetWorld(); 187 | FVector CurrentVelocity = GetOwner()->GetVelocity(); 188 | 189 | if (!bGaitActive) 190 | { 191 | bLastFrameWasDisable = true; 192 | } 193 | 194 | 195 | // force 60 fps refresh rate 196 | const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 197 | if (LODSetting.bForceDeltaTimeAtTargetFPS) 198 | { 199 | DeltaTime = 1.f / LODSetting.TargetFPS; 200 | } 201 | 202 | // Update Gaits Data. 203 | if (bGaitActive && GaitsData.Contains(CurrentGaitMode)) 204 | { 205 | const UGaitDataAsset& CurrentAsset = *GaitsData[CurrentGaitMode]; 206 | 207 | //if (bGaitActive) 208 | //{ 209 | UpdateEffectors(CurrentAsset); 210 | if (bLastFrameWasDisable) 211 | { 212 | bLastFrameWasDisable = false; 213 | return; 214 | } 215 | //} 216 | 217 | bool bZeroVelocity = CurrentVelocity.SizeSquared() == 0.f; 218 | bool bCond = CurrentAsset.bComputeWithVelocityOnly ? !bZeroVelocity : true; 219 | 220 | if (bCond) 221 | { 222 | if (!bZeroVelocity) 223 | { 224 | LastVelocity = CurrentVelocity; 225 | } 226 | 227 | AnimInstanceRef->Execute_SetProceduralGaitEnable(AnimInstanceRef, true); 228 | 229 | // Step 1: Timers. 230 | TimeBuffer += (DeltaTime * CurrentAsset.GetFrameRatio() * PlayRate); 231 | CurrentTime = FMath::Fmod(TimeBuffer, 1.f); 232 | 233 | TArray SwingValuesKeys; 234 | CurrentAsset.GaitSwingValues.GetKeys(SwingValuesKeys); 235 | 236 | // Step 2: Foreach swing values, we will check if we are in 'Swing' or 'Stance' according to @CurrentTime. 237 | for (int j = 0, m = SwingValuesKeys.Num(); j < m; ++j) 238 | { 239 | FName Key = SwingValuesKeys[j]; 240 | const FGaitSwingData& CurrentData = CurrentAsset.GaitSwingValues[Key]; 241 | 242 | if (Effectors.Contains(Key)) 243 | { 244 | FGaitEffectorData& Effector = Effectors[Key]; 245 | 246 | // Update blend value 247 | { 248 | // If there isn't any pending gait, so it's a blend in 249 | if (PendingGaitMode.IsNone() || Effector.CurrentGait == PendingGaitMode) 250 | { 251 | if (CurrentData.BlendData.BlendInTime == 0) 252 | { 253 | Effector.CurrentBlendValue = 1.f; 254 | } 255 | else 256 | { 257 | bBlendIn = true; 258 | if (Effector.CurrentBlendValue < 1.f) 259 | { 260 | Effector.CurrentBlendValue = FMath::Clamp(Effector.CurrentBlendValue + (DeltaTime / CurrentData.BlendData.BlendInTime), 0.f, 1.f); 261 | } 262 | } 263 | 264 | if (PendingGaitMode.IsNone()) 265 | { 266 | bAllEffectorBlendOutEnd = false; 267 | } 268 | } 269 | else 270 | { 271 | if (CurrentData.BlendData.BlendOutTime == 0) 272 | { 273 | Effector.CurrentBlendValue = 0.f; 274 | Effector.CurrentGait = PendingGaitMode; 275 | } 276 | else 277 | { 278 | bBlendIn = false; 279 | if (Effector.CurrentBlendValue > 0.f) 280 | { 281 | Effector.CurrentBlendValue = FMath::Clamp(Effector.CurrentBlendValue - (DeltaTime / CurrentData.BlendData.BlendOutTime), 0.f, 1.f); 282 | } 283 | 284 | // if end blend out, swap gait. 285 | if (Effector.CurrentBlendValue == 0.f) 286 | { 287 | Effector.CurrentGait = PendingGaitMode; 288 | } 289 | } 290 | bAllEffectorBlendOutEnd = false; 291 | } 292 | } 293 | 294 | // no need to check, check is made when @UpdateGaitMode. 295 | const UGaitDataAsset& UpdatedCurrentAsset = *GaitsData[Effector.CurrentGait.IsNone() ? CurrentGaitMode : Effector.CurrentGait]; 296 | if (UpdatedCurrentAsset.GaitSwingValues.Contains(Key)) 297 | { 298 | const FGaitSwingData& UpdatedCurrentData = UpdatedCurrentAsset.GaitSwingValues[Key]; 299 | 300 | // Step 2.1: Check if valid swing data. 301 | /*if (Effector.bForceSwing) 302 | { 303 | if (!CurrentData.CorrectionData.CorrectionSwingTranslationCurve) 304 | { 305 | DEBUG_LOG_FORMAT(Warning, "Correction is query but there is no valid CorrectionSwingTranslationCurve. From actor %s.", *GetOwner()->GetName()); 306 | continue; 307 | } 308 | }*/ 309 | /*else if ((!CurrentData.TranslationData.SwingTranslationCurve || CurrentData.TranslationData.TranslationFactor == FVector::ZeroVector) 310 | && (!CurrentData.RotationData.SwingRotationCurve || CurrentData.RotationData.RotationFactor == FVector::ZeroVector)) 311 | { 312 | continue; 313 | }*/ 314 | 315 | bool bCanCompute = Effector.BlockTime == -1.f 316 | || (Effector.BlockTime > UpdatedCurrentData.BeginSwing && CurrentTime >= Effector.BlockTime) 317 | || (Effector.BlockTime < UpdatedCurrentData.BeginSwing && CurrentTime < UpdatedCurrentData.BeginSwing && CurrentTime >= Effector.BlockTime); 318 | 319 | if (bCanCompute) 320 | { 321 | float MinRange, MaxRange; 322 | bool InRange = IsInRange(CurrentTime, 323 | Effector.bForceSwing ? Effector.BeginForceSwingInterval : UpdatedCurrentData.BeginSwing, 324 | Effector.bForceSwing ? Effector.EndForceSwingInterval : UpdatedCurrentData.EndSwing, 325 | MinRange, MaxRange); 326 | 327 | // Step 2.2: If the effector is in 'Swing'. 328 | if (InRange) 329 | { 330 | Effector.bCorrectionIK = false; 331 | 332 | float CurrentCurvePosition = FMath::GetMappedRangeValueClamped(FVector2D(MinRange, MaxRange), FVector2D(0.f, 1.f), CurrentTime); 333 | float lerpSpeed = UpdatedCurrentData.TranslationData.LerpSpeed * (Effector.CurrentBlendValue == 1.f ? 1.f : (bBlendIn ? (UpdatedCurrentData.BlendData.BlendInAcceleration ? UpdatedCurrentData.BlendData.BlendInAcceleration->GetFloatValue(Effector.CurrentBlendValue) : 1.f) : (UpdatedCurrentData.BlendData.BlendOutAcceleration ? UpdatedCurrentData.BlendData.BlendOutAcceleration->GetFloatValue(Effector.CurrentBlendValue) : 1.f))); 334 | 335 | // Step 2.2.1: Apply 'Swing' rotation (for bones). 336 | if (UpdatedCurrentData.RotationData.bAffectEffector) 337 | { 338 | FVector CurrentCurveValue = UpdatedCurrentData.RotationData.RotationFactor * (UpdatedCurrentData.RotationData.SwingRotationCurve ? 339 | UpdatedCurrentData.RotationData.SwingRotationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1)); 340 | 341 | AnimInstanceRef->Execute_UpdateEffectorRotation(AnimInstanceRef, Key, FRotator(CurrentCurveValue.X, CurrentCurveValue.Y, CurrentCurveValue.Z)/*OwnedMesh->GetComponentRotation().RotateVector(CurrentCurveValue).Rotation()*/, lerpSpeed); 342 | } 343 | 344 | // Step 2.2.1: Apply 'Swing' translation (for effectors IK(socket)). 345 | //if (UpdatedCurrentData.TranslationData.SwingTranslationCurve || (Effector.bForceSwing && UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve)) 346 | if (UpdatedCurrentData.TranslationData.bAffectEffector) 347 | { 348 | FVector CurrentCurveValue = UpdatedCurrentData.TranslationData.TranslationFactor * UpdatedCurrentData.TranslationData.TranslationSwingScale 349 | * (Effector.bForceSwing ? (UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve ? UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1)) 350 | : (UpdatedCurrentData.TranslationData.SwingTranslationCurve ? UpdatedCurrentData.TranslationData.SwingTranslationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1))); 351 | 352 | CurrentCurveValue = UpdatedCurrentData.TranslationData.bOrientToVelocity ? 353 | ORIENT_TO_VELOCITY(CurrentCurveValue) : OwnedMesh->GetComponentRotation().RotateVector(CurrentCurveValue); 354 | 355 | FVector Offset = UpdatedCurrentData.TranslationData.Offset * UpdatedCurrentData.TranslationData.TranslationFactor; 356 | Offset = UpdatedCurrentData.TranslationData.bOrientToVelocity ? 357 | ORIENT_TO_VELOCITY(Offset) : OwnedMesh->GetComponentRotation().RotateVector(Offset); 358 | 359 | CurrentEffectorLocation = Effector.CurrentEffectorLocation; 360 | IdealEffectorLocation = Effector.IdealEffectorLocation; 361 | NewCurrentLocation = Effector.IdealEffectorLocation + Offset + CurrentCurveValue; 362 | Treshold = UpdatedCurrentData.CorrectionData.DistanceTresholdToAdjust; 363 | bForceSwing = Effector.bForceSwing; 364 | 365 | if (UpdatedCurrentData.TranslationData.bAdaptToGroundLevel) 366 | { 367 | NewCurrentLocation.Z += Effector.IdealEffectorLocation.Z - Effector.GroundLocation.Z; 368 | } 369 | 370 | AnimInstanceRef->Execute_UpdateEffectorTranslation(AnimInstanceRef, Key, NewCurrentLocation, !bLastFrameWasDisable, lerpSpeed); 371 | 372 | Effector.CurrentEffectorLocation = NewCurrentLocation; 373 | } 374 | } 375 | // Step 2.3: If the effector is in 'Stance'. 376 | else 377 | { 378 | if (Effector.bForceSwing) 379 | { 380 | Effector.BlockTime = UpdatedCurrentData.EndSwing >= 0.99f ? 0.f : UpdatedCurrentData.EndSwing; 381 | } 382 | else 383 | { 384 | Effector.BlockTime = -1.f; 385 | 386 | // check for foot ik. 387 | if (!bLastFrameWasDisable && World && !Effector.bCorrectionIK && UpdatedCurrentData.CorrectionData.bComputeCollision) 388 | { 389 | //const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 390 | if (LODSetting.bCanComputeCollisionCorrection) 391 | { 392 | NOBUNANIM_SCOPE_COUNTER(ProceduralGait_StanceIKCorrection); 393 | 394 | // Get origin for IK 395 | FVector Origin; 396 | if (UpdatedCurrentData.CorrectionData.bUseCurrentEffector) 397 | { 398 | Origin = Effector.CurrentEffectorLocation; 399 | } 400 | else 401 | { 402 | Origin = UpdatedCurrentData.CorrectionData.OriginCollisionSocketName.IsNone() ? 403 | OwnedMesh->GetSocketLocation(Key) : 404 | OwnedMesh->GetSocketLocation(UpdatedCurrentData.CorrectionData.OriginCollisionSocketName); 405 | } 406 | FVector Dir = UpdatedCurrentData.CorrectionData.bOrientToVelocity ? ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.AbsoluteDirection) : UpdatedCurrentData.CorrectionData.AbsoluteDirection; 407 | 408 | // Get Dest 409 | FVector Dest = Origin + Dir; 410 | 411 | // Add inverse absolute direction 412 | Origin -= Dir; 413 | 414 | FCollisionQueryParams SweepParam(*GetOwner()->GetName(), false, GetOwner()); 415 | TArray HitResults; 416 | bool bFoundHit = TraceRay(World, HitResults, Origin, Dest, UpdatedCurrentData.CorrectionData.TraceChannel, SPHERECAST_IK_CORRECTION_RADIUS); 417 | 418 | 419 | if (bFoundHit) 420 | { 421 | 422 | FHitResult& HitResult = GetBestHitResult(HitResults, Origin); 423 | 424 | { 425 | #if WITH_EDITOR 426 | if (LODSetting.Debug.bShowCollisionCorrection) 427 | { 428 | if (HitResult.bBlockingHit) 429 | { 430 | DrawDebugPoint(World, HitResult.ImpactPoint + ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.CollisionSnapOffset), 10.f, FColor::Red, false, 3.f); 431 | } 432 | } 433 | #endif 434 | } 435 | 436 | // if hit ground 437 | if (HitResult.bBlockingHit) 438 | { 439 | Effector.CurrentEffectorLocation = HitResult.ImpactPoint + ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.CollisionSnapOffset); 440 | Effector.bCorrectionIK = true; 441 | if (UpdatedCurrentData.EventData.bRaiseOnCollisionEvent) 442 | { 443 | OnCollisionEvent.Broadcast(Key, Effector.CurrentEffectorLocation); 444 | } 445 | } 446 | } 447 | } 448 | } 449 | 450 | AnimInstanceRef->Execute_UpdateEffectorTranslation(AnimInstanceRef, Key, Effector.CurrentEffectorLocation, !bLastFrameWasDisable, UpdatedCurrentData.TranslationData.LerpSpeed); 451 | } 452 | 453 | bForceSwing = Effector.bForceSwing = false; 454 | //Effector.BeginForceSwingInterval = CurrentAsset.GaitSwingValues[Key].BeginSwing; 455 | //Effector.EndForceSwingInterval = CurrentAsset.GaitSwingValues[Key].EndSwing; 456 | 457 | CurrentEffectorLocation = Effector.CurrentEffectorLocation; 458 | IdealEffectorLocation = Effector.IdealEffectorLocation; 459 | NewCurrentLocation = CurrentEffectorLocation;// Effector.IdealEffectorLocation + TranslationData.Offset + CurrentCurveValue; 460 | Treshold = UpdatedCurrentData.CorrectionData.DistanceTresholdToAdjust; 461 | 462 | // Step 2.3.1: Check if the effector need to be adjusted 463 | if (!bLastFrameWasDisable && CurrentAsset.GaitSwingValues[Key].CorrectionData.bAutoAdjustWithIdealEffector) 464 | { 465 | float VectorLength = (Effector.CurrentEffectorLocation - Effector.IdealEffectorLocation).Size2D(); 466 | 467 | // Is the distance breaking treshold? 468 | if (VectorLength >= CurrentAsset.GaitSwingValues[Key].CorrectionData.DistanceTresholdToAdjust) 469 | { 470 | // Here we compute the new interval of swing. 471 | float Begin = CurrentAsset.GaitSwingValues[Key].BeginSwing; 472 | float End = CurrentAsset.GaitSwingValues[Key].EndSwing; 473 | 474 | float Alpha = 0.f; 475 | if (Begin > End) 476 | { 477 | Alpha = (1.f - Begin) + End; 478 | } 479 | else 480 | { 481 | Alpha = End - Begin; 482 | } 483 | 484 | float Beta = CurrentTime + Alpha; 485 | // if the end swing is > 1 (absolute time) we just consider that the swing will end the next cycle. 486 | if (Beta >= 1.f) 487 | { 488 | Beta -= 1.f; 489 | } 490 | 491 | Effector.BeginForceSwingInterval = CurrentTime; 492 | Effector.EndForceSwingInterval = Beta; 493 | bForceSwing = Effector.bForceSwing = true; 494 | Effector.BlockTime = -1.f; 495 | } 496 | } 497 | 498 | 499 | } 500 | 501 | #if WITH_EDITOR 502 | // Step 3: Draw Debug. 503 | { 504 | DrawGaitDebug(NewCurrentLocation, IdealEffectorLocation, CurrentEffectorLocation, Treshold, 505 | CurrentAsset.GaitSwingValues[Key].CorrectionData.bAutoAdjustWithIdealEffector, bForceSwing, 506 | &CurrentAsset.GaitSwingValues[Key].DebugData); 507 | } 508 | #endif 509 | } 510 | } 511 | } 512 | } 513 | 514 | // Step 3: if all effector end blend out swtich gait 515 | if (bAllEffectorBlendOutEnd == true) 516 | { 517 | CurrentGaitMode = PendingGaitMode; 518 | PendingGaitMode = ""; 519 | } 520 | } 521 | else 522 | { 523 | CurrentTime = 0; 524 | AnimInstanceRef->Execute_SetProceduralGaitEnable(AnimInstanceRef, false); 525 | } 526 | 527 | } 528 | else 529 | { 530 | CurrentTime = 0; 531 | if (AnimInstanceRef) 532 | { 533 | AnimInstanceRef->Execute_SetProceduralGaitEnable(AnimInstanceRef, false); 534 | } 535 | } 536 | 537 | #if WITH_EDITOR 538 | UpdateLOD(true); 539 | #else 540 | UpdateLOD(); 541 | #endif 542 | 543 | } 544 | 545 | void UProceduralGaitControllerComponent::ComputeCollisionCorrection(const FGaitCorrectionData* CorrectionData, FGaitEffectorData& Effector) 546 | { 547 | 548 | } 549 | 550 | void UProceduralGaitControllerComponent::UpdateGaitMode_Implementation(const FName& NewGaitName) 551 | { 552 | if (GaitsData.Contains(NewGaitName)) 553 | { 554 | if (CurrentGaitMode != NewGaitName) 555 | { 556 | if (CurrentGaitMode.IsNone() || CurrentGaitMode == "None") 557 | { 558 | CurrentGaitMode = NewGaitName; 559 | } 560 | else 561 | { 562 | PendingGaitMode = NewGaitName; 563 | } 564 | //CurrentGaitMode = NewGaitName; 565 | SetComponentTickEnabled(true); 566 | } 567 | } 568 | else 569 | { 570 | SetComponentTickEnabled(false); 571 | //DEBUG_LOG_FORMAT(Warning, "Invalid NewGaitName %s. There is no gait data corresponding. Ignored.", NewGaitName); 572 | } 573 | } 574 | 575 | void UProceduralGaitControllerComponent::UpdateEffectors(const UGaitDataAsset& CurrentAsset) 576 | { 577 | NOBUNANIM_SCOPE_COUNTER(ProceduralGait_UpdateEffectors); 578 | 579 | TArray Keys; 580 | GaitsData.GetKeys(Keys); 581 | 582 | for (int i = 0, n = Keys.Num(); i < n; ++i) 583 | { 584 | TArray SwingValuesKeys; 585 | TMap& SwingValues = GaitsData[Keys[i]]->GaitSwingValues; 586 | SwingValues.GetKeys(SwingValuesKeys); 587 | 588 | for (int j = 0, m = SwingValuesKeys.Num(); j < m; ++j) 589 | { 590 | FName Key = SwingValuesKeys[j]; 591 | FVector EffectorLocation = OwnedMesh->GetSocketLocation(Key); 592 | if (Effectors.Contains(Key)) 593 | { 594 | Effectors[Key].IdealEffectorLocation = EffectorLocation; 595 | 596 | if (CurrentAsset.GaitSwingValues.Contains(Key) && CurrentAsset.GaitSwingValues[Key].TranslationData.bAdaptToGroundLevel) 597 | { 598 | FVector GroundLocation; 599 | TArray HitResults; 600 | bool bFound = TraceRay(GetWorld(), HitResults, EffectorLocation, EffectorLocation + FVector(0, 0, -100.f), ECollisionChannel::ECC_WorldStatic, SPHERECAST_IK_CORRECTION_RADIUS); 601 | if (!bFound) 602 | { 603 | GroundLocation = EffectorLocation + FVector(0, 0, -100.f); 604 | } 605 | else 606 | { 607 | FHitResult& HitResult = GetBestHitResult(HitResults, EffectorLocation); 608 | GroundLocation = HitResult.ImpactPoint; 609 | } 610 | 611 | Effectors[Key].GroundLocation = GroundLocation; 612 | } 613 | 614 | if (bLastFrameWasDisable) 615 | { 616 | Effectors[Key].CurrentEffectorLocation = EffectorLocation; 617 | AnimInstanceRef->Execute_UpdateEffectorTranslation(AnimInstanceRef, Key, EffectorLocation, false, 0); 618 | } 619 | } 620 | else 621 | { 622 | FGaitEffectorData Effector; 623 | Effector.IdealEffectorLocation = EffectorLocation; 624 | Effectors.Add(Key, Effector); 625 | } 626 | } 627 | } 628 | } 629 | 630 | 631 | bool UProceduralGaitControllerComponent::IsInRange(float Value, float Min, float Max, float& OutRangeMin, float& OutRangeMax) 632 | { 633 | bool A = Value >= Min && Value <= Max; 634 | bool APrime = Value >= Max; 635 | 636 | // Gave me headache ._. 637 | OutRangeMin = A ? Min : (APrime ? Min : Min - 1.f); 638 | OutRangeMax = A ? Max : (APrime ? 1.f /*- Min + Min*/ + Max : Max); 639 | 640 | if (A) 641 | { 642 | return true; 643 | } 644 | else 645 | { 646 | bool B1 = Value <= Min && Value <= Max; 647 | bool B2 = Value >= Min && Value >= Max; 648 | bool B3 = B1 || B2; 649 | bool C = Min >= Max && B3; 650 | 651 | if (C) 652 | { 653 | return true; 654 | } 655 | else 656 | { 657 | return false; 658 | } 659 | } 660 | } 661 | 662 | void UProceduralGaitControllerComponent::DrawGaitDebug(FVector Position, FVector EffectorLocation, FVector CurrentLocation, float Treshold, bool bAutoAdjustWithIdealEffector, bool bForceSwing, const FGaitDebugData* DebugData) 663 | { 664 | if (bShowDebug && DebugData->bDrawDebug) 665 | { 666 | UWorld* World = GetWorld(); 667 | if (World) 668 | { 669 | DrawDebugPoint(World, Position, 2, FColor::Yellow, false, 1.f); 670 | DrawDebugSphere(World, Position, 2, 12, FColor(255.f, 130.f, 0.f), false, 0.f); 671 | DrawDebugSphere(World, EffectorLocation, 2, 12, FColor(255.f, 0.f, 0.f), false, 0.f); 672 | 673 | DrawDebugLine(World, EffectorLocation, Position, FColor(255.f, 255.f, 255.f), false, 0.f, 0, 0.5f); 674 | //DrawDebugLine(World, CurrentLocation, EffectorLocation, FColor(255.f, 0.f, 0.f), false, 0.f, 0, 1.f); 675 | 676 | FRotator Rot = LastVelocity.Rotation(); 677 | DrawDebugDirectionalArrow(World, EffectorLocation, EffectorLocation + (Rot.RotateVector(FVector::ForwardVector) * 10.f), 1, DebugData->VelocityColor, false, 0, 0, 1.f); 678 | 679 | if (bAutoAdjustWithIdealEffector) 680 | { 681 | DrawDebugCircle(World, FVector(CurrentLocation.X, CurrentLocation.Y, EffectorLocation.Z), Treshold, 20, bForceSwing ? DebugData->ForceSwingColor : DebugData->CorrectionCircleColor, false, 0.f, 0, bForceSwing ? 1.f : 0.75f, FVector(0,1,0), FVector(1,0,0), false); 682 | } 683 | } 684 | } 685 | 686 | #if WITH_EDITOR 687 | if (UNobunanimSettings::GetLODSetting(CurrentLOD).Debug.bShowLOD 688 | || bShowLOD 689 | ) 690 | { 691 | UWorld* World = GetWorld(); 692 | if (World) 693 | { 694 | DrawDebugSolidBox(World, GetOwner()->GetActorLocation(), GetOwner()->GetSimpleCollisionCylinderExtent(), UNobunanimSettings::GetLODSetting(CurrentLOD).Debug.LODColor, false, 0.f); 695 | } 696 | } 697 | #endif 698 | } 699 | 700 | 701 | #if WITH_EDITOR 702 | void UProceduralGaitControllerComponent::ShowGaitDebug() 703 | { 704 | bShowDebug = !bShowDebug; 705 | } 706 | 707 | void UProceduralGaitControllerComponent::ShowGaitLOD() 708 | { 709 | bShowLOD = !bShowLOD; 710 | } 711 | #endif 712 | 713 | void UProceduralGaitControllerComponent::UpdateLOD(bool bForceUpdate) 714 | { 715 | if (CurrentLOD != OwnedMesh->PredictedLODLevel 716 | || bForceUpdate) 717 | { 718 | SetComponentTickInterval(1.f / (float)UNobunanimSettings::GetLODSetting(CurrentLOD = OwnedMesh->PredictedLODLevel).TargetFPS);// LODTargetFPS[CurrentLOD = OwnedMesh->PredictedLODLevel]); 719 | } 720 | } 721 | 722 | -------------------------------------------------------------------------------- /Source/Nobunanim/Private/ProceduralGaitAnimInstance.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralGaitAnimInstance.h" 5 | 6 | #include "Nobunanim/Private/Nobunanim.h" 7 | #include "Nobunanim/Public/GaitDataAsset.h" 8 | #include "Nobunanim/Public/NobunanimSettings.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #define ORIENT_TO_VELOCITY(Input) LastVelocity.Rotation().RotateVector(Input) 16 | 17 | #define SPHERECAST_IK_CORRECTION_RADIUS 30.f 18 | #define MAX_DELTATIME_CLAMP (1.f / 30.f) 19 | 20 | 21 | 22 | void FProceduralGaitAnimInstanceTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) 23 | { 24 | if (Target && !Target->IsPendingKillOrUnreachable()) 25 | { 26 | if (TickType != LEVELTICK_ViewportsOnly)// || Target->ShouldTickIfViewportsOnly()) 27 | { 28 | FScopeCycleCounterUObject ActorScope(Target); 29 | //Target->ProceduralGaitUpdate(DeltaTime);//, TickType, *this); 30 | } 31 | } 32 | } 33 | 34 | FString FProceduralGaitAnimInstanceTickFunction::DiagnosticMessage() 35 | { 36 | return Target->GetFullName() + TEXT("[ProceduralGaitUpdate]"); 37 | } 38 | 39 | FName FProceduralGaitAnimInstanceTickFunction::DiagnosticContext(bool bDetailed) 40 | { 41 | if (bDetailed) 42 | { 43 | // Format is "ActorNativeClass/ActorClass" 44 | FString ContextString = FString::Printf(TEXT("%s/%s"), *GetParentNativeClass(Target->GetClass())->GetName(), *Target->GetClass()->GetName()); 45 | return FName(*ContextString); 46 | } 47 | else 48 | { 49 | return GetParentNativeClass(Target->GetClass())->GetFName(); 50 | } 51 | } 52 | 53 | 54 | FVector GetAverage(const TArray& Vectors) 55 | { 56 | FVector Sum(0.f); 57 | FVector Average(0.f); 58 | 59 | if (Vectors.Num() > 0) 60 | { 61 | for (int32 VecIdx = 0; VecIdx < Vectors.Num(); VecIdx++) 62 | { 63 | Sum += Vectors[VecIdx]; 64 | } 65 | 66 | Average = Sum / ((float)Vectors.Num()); 67 | } 68 | 69 | return Average; 70 | } 71 | 72 | 73 | 74 | void UProceduralGaitAnimInstance::NativeUpdateAnimation(float DeltaSeconds) 75 | { 76 | Super::NativeUpdateAnimation(DeltaSeconds); 77 | 78 | NOBUNANIM_SCOPE_COUNTER(TerrainPrediction); 79 | 80 | //DeltaTime = DeltaSeconds; 81 | 82 | if (!OwnedMesh) 83 | { 84 | OwnedMesh = GetOwningComponent(); 85 | } 86 | 87 | //CurrentLOD = OwnedMesh->PredictedLODLevel; 88 | 89 | FRotator Rotation(0,0,0); 90 | 91 | if (CurrentLOD == 0) 92 | { 93 | Rotation = ComputeGroundReflection_LOD0(); 94 | } 95 | else //if (CurrentLOD == 1) 96 | { 97 | Rotation = ComputeGroundReflection_LOD1(); 98 | } 99 | 100 | GroundReflectionRotation = FMath::Lerp(GroundReflectionRotation, Rotation.GetInverse(), DeltaSeconds * GroundReflectionLerpSpeed); 101 | 102 | UpdateLOD(); 103 | 104 | 105 | //ProceduralGaitUpdate(); 106 | } 107 | 108 | void UProceduralGaitAnimInstance::NativeBeginPlay() 109 | { 110 | Super::NativeBeginPlay(); 111 | 112 | PrimaryAnimInstanceTick.TickGroup = TG_PrePhysics; 113 | // Default to no tick function, but if we set 'never ticks' to false (so there is a tick function) it is enabled by default 114 | PrimaryAnimInstanceTick.bCanEverTick = true; 115 | PrimaryAnimInstanceTick.bStartWithTickEnabled = true; 116 | 117 | UpdateLOD(true); 118 | /*ACharacter* Chara = Cast(GetOwningActor()); 119 | if (Chara) 120 | { 121 | PrimaryAnimInstanceTick.AddPrerequisite(Chara, Chara->PrimaryActorTick); 122 | PrimaryAnimInstanceTick.SetTickFunctionEnable(Chara->PrimaryActorTick.IsTickFunctionEnabled()); 123 | PrimaryAnimInstanceTick.Target = this; 124 | PrimaryAnimInstanceTick.RegisterTickFunction(Chara->GetLevel()); 125 | }*/ 126 | } 127 | 128 | 129 | #pragma region PROCEDURAL GAIT INTERFACE 130 | 131 | void UProceduralGaitAnimInstance::ProceduralGaitUpdate() 132 | { 133 | if (!bGaitActive) 134 | { 135 | return; 136 | } 137 | 138 | NOBUNANIM_SCOPE_COUNTER(Gait_Update); 139 | 140 | FVector NewCurrentLocation; 141 | FVector IdealEffectorLocation; 142 | FVector CurrentEffectorLocation; 143 | float Treshold = 0.f; 144 | bool bForceSwing = false; 145 | bool bAllEffectorBlendOutEnd = true; 146 | 147 | UWorld* World = GetWorld(); 148 | FVector CurrentVelocity = GetOwningActor()->GetVelocity(); 149 | 150 | /*if (!bGaitActive) 151 | { 152 | bLastFrameWasDisable = true; 153 | }*/ 154 | 155 | 156 | //DeltaTime = _DeltaTime; 157 | DeltaTime = World->TimeSince(LastTime);// FMath::Min(World->TimeSince(LastTime), MAX_DELTATIME_CLAMP); 158 | LastTime = World->TimeSeconds; 159 | 160 | // force 60 fps refresh rate 161 | const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 162 | if (LODSetting.bForceDeltaTimeAtTargetFPS) 163 | { 164 | DeltaTime = 1.f / LODSetting.TargetFPS; 165 | } 166 | 167 | // Update Gaits Data. 168 | if (GaitsData.Contains(CurrentGaitMode)) 169 | { 170 | const UGaitDataAsset& CurrentAsset = *GaitsData[CurrentGaitMode]; 171 | 172 | //if (bGaitActive) 173 | //{ 174 | UpdateEffectors(CurrentAsset); 175 | /*if (bLastFrameWasDisable) 176 | { 177 | bLastFrameWasDisable = false; 178 | return; 179 | }*/ 180 | //} 181 | 182 | bool bZeroVelocity = CurrentVelocity.SizeSquared() == 0.f; 183 | bool bCond = CurrentAsset.bComputeWithVelocityOnly ? !bZeroVelocity : true; 184 | 185 | if (bCond) 186 | { 187 | if (!bZeroVelocity) 188 | { 189 | LastVelocity = CurrentVelocity; 190 | } 191 | 192 | //Execute_SetProceduralGaitEnable(this, true); 193 | 194 | // Step 1: Timers. 195 | TimeBuffer += (DeltaTime * CurrentAsset.GetFrameRatio() * PlayRate); 196 | CurrentTime = FMath::Fmod(TimeBuffer, 1.f); 197 | 198 | TArray SwingValuesKeys; 199 | CurrentAsset.GaitSwingValues.GetKeys(SwingValuesKeys); 200 | 201 | // Step 2: Foreach swing values, we will check if we are in 'Swing' or 'Stance' according to @CurrentTime. 202 | for (int j = 0, m = SwingValuesKeys.Num(); j < m; ++j) 203 | { 204 | NOBUNANIM_SCOPE_COUNTER(Gait_Evaluate_PerEffector); 205 | 206 | FName Key = SwingValuesKeys[j]; 207 | const FGaitSwingData& CurrentData = CurrentAsset.GaitSwingValues[Key]; 208 | 209 | if (Effectors.Contains(Key)) 210 | { 211 | FGaitEffectorData& Effector = Effectors[Key]; 212 | 213 | bool bBlendIn; 214 | 215 | // Update blend value 216 | { 217 | // If there isn't any pending gait, so it's a blend in 218 | if (PendingGaitMode.IsNone() || Effector.CurrentGait == PendingGaitMode) 219 | { 220 | if (CurrentData.BlendData.BlendInTime == 0) 221 | { 222 | Effector.CurrentBlendValue = 1.f; 223 | } 224 | else 225 | { 226 | bBlendIn = true; 227 | if (Effector.CurrentBlendValue < 1.f) 228 | { 229 | Effector.CurrentBlendValue = FMath::Clamp(Effector.CurrentBlendValue + (DeltaTime / CurrentData.BlendData.BlendInTime), 0.f, 1.f); 230 | } 231 | } 232 | 233 | if (PendingGaitMode.IsNone()) 234 | { 235 | bAllEffectorBlendOutEnd = false; 236 | } 237 | } 238 | else 239 | { 240 | if (CurrentData.BlendData.BlendOutTime == 0) 241 | { 242 | Effector.CurrentBlendValue = 0.f; 243 | Effector.CurrentGait = PendingGaitMode; 244 | } 245 | else 246 | { 247 | bBlendIn = false; 248 | if (Effector.CurrentBlendValue > 0.f) 249 | { 250 | Effector.CurrentBlendValue = FMath::Clamp(Effector.CurrentBlendValue - (DeltaTime / CurrentData.BlendData.BlendOutTime), 0.f, 1.f); 251 | } 252 | 253 | // if end blend out, swap gait. 254 | if (Effector.CurrentBlendValue == 0.f) 255 | { 256 | Effector.CurrentGait = PendingGaitMode; 257 | } 258 | } 259 | bAllEffectorBlendOutEnd = false; 260 | } 261 | } 262 | 263 | // no need to check, check is made when @UpdateGaitMode. 264 | const UGaitDataAsset& UpdatedCurrentAsset = *GaitsData[Effector.CurrentGait.IsNone() ? CurrentGaitMode : Effector.CurrentGait]; 265 | if (UpdatedCurrentAsset.GaitSwingValues.Contains(Key)) 266 | { 267 | const FGaitSwingData& UpdatedCurrentData = UpdatedCurrentAsset.GaitSwingValues[Key]; 268 | 269 | // Step 2.1: Check if valid swing data. 270 | /*if (Effector.bForceSwing) 271 | { 272 | if (!CurrentData.CorrectionData.CorrectionSwingTranslationCurve) 273 | { 274 | DEBUG_LOG_FORMAT(Warning, "Correction is query but there is no valid CorrectionSwingTranslationCurve. From actor %s.", *GetOwner()->GetName()); 275 | continue; 276 | } 277 | }*/ 278 | /*else if ((!CurrentData.TranslationData.SwingTranslationCurve || CurrentData.TranslationData.TranslationFactor == FVector::ZeroVector) 279 | && (!CurrentData.RotationData.SwingRotationCurve || CurrentData.RotationData.RotationFactor == FVector::ZeroVector)) 280 | { 281 | continue; 282 | }*/ 283 | 284 | float BeginSwing = (UpdatedCurrentAsset.GaitSwingValues.Contains(UpdatedCurrentData.SwingTime.ParentEffector) ? (UpdatedCurrentAsset.GaitSwingValues[Key].BeginSwing) : UpdatedCurrentData.BeginSwing); 285 | float EndSwing = (UpdatedCurrentAsset.GaitSwingValues.Contains(UpdatedCurrentData.SwingTime.ParentEffector) ? (UpdatedCurrentAsset.GaitSwingValues[Key].EndSwing) : UpdatedCurrentData.EndSwing); 286 | 287 | bool bCanCompute = Effector.BlockTime == -1.f 288 | || (Effector.BlockTime > BeginSwing && CurrentTime >= Effector.BlockTime) 289 | || (Effector.BlockTime < BeginSwing && CurrentTime < BeginSwing && CurrentTime >= Effector.BlockTime); 290 | 291 | if (bCanCompute) 292 | { 293 | float MinRange, MaxRange; 294 | BeginSwing = Effector.bForceSwing ? Effector.BeginForceSwingInterval : BeginSwing; 295 | EndSwing = Effector.bForceSwing ? Effector.EndForceSwingInterval : EndSwing; 296 | 297 | bool InRange = IsInRange(CurrentTime, BeginSwing, EndSwing, MinRange, MaxRange); 298 | 299 | // Step 2.2: If the effector is in 'Swing'. 300 | if (InRange) 301 | { 302 | NOBUNANIM_SCOPE_COUNTER(Gait_Swing); 303 | 304 | Effector.bCorrectionIK = false; 305 | 306 | float CurrentCurvePosition = FMath::GetMappedRangeValueClamped(FVector2D(MinRange, MaxRange), FVector2D(0.f, 1.f), CurrentTime); 307 | 308 | // :D hue hue :D 309 | float lerpSpeed = UpdatedCurrentData.TranslationData.LerpSpeed <= 0 ? 0 310 | : UpdatedCurrentData.TranslationData.LerpSpeed * (Effector.CurrentBlendValue == 1.f ? 1.f 311 | : (bBlendIn ? (UpdatedCurrentData.BlendData.BlendInAcceleration ? UpdatedCurrentData.BlendData.BlendInAcceleration->GetFloatValue(Effector.CurrentBlendValue) 312 | : 1.f) 313 | : (UpdatedCurrentData.BlendData.BlendOutAcceleration ? UpdatedCurrentData.BlendData.BlendOutAcceleration->GetFloatValue(Effector.CurrentBlendValue) 314 | : 1.f))); 315 | 316 | // Step 2.2.1: Apply 'Swing' rotation (for bones). 317 | if (UpdatedCurrentData.RotationData.bAffectEffector) 318 | { 319 | FVector CurrentCurveValue = UpdatedCurrentData.RotationData.RotationFactor * (UpdatedCurrentData.RotationData.SwingRotationCurve ? 320 | UpdatedCurrentData.RotationData.SwingRotationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1)); 321 | 322 | Execute_UpdateEffectorRotation(this, Key, FRotator(CurrentCurveValue.X, CurrentCurveValue.Y, CurrentCurveValue.Z)/*OwnedMesh->GetComponentRotation().RotateVector(CurrentCurveValue).Rotation()*/, lerpSpeed); 323 | } 324 | 325 | // Step 2.2.1: Apply 'Swing' translation (for effectors IK(socket)). 326 | //if (UpdatedCurrentData.TranslationData.SwingTranslationCurve || (Effector.bForceSwing && UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve)) 327 | if (UpdatedCurrentData.TranslationData.bAffectEffector) 328 | { 329 | FVector CurrentCurveValue = UpdatedCurrentData.TranslationData.TranslationFactor * UpdatedCurrentData.TranslationData.TranslationSwingScale 330 | * (Effector.bForceSwing ? (UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve ? UpdatedCurrentData.CorrectionData.CorrectionSwingTranslationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1)) 331 | : (UpdatedCurrentData.TranslationData.SwingTranslationCurve ? UpdatedCurrentData.TranslationData.SwingTranslationCurve->GetVectorValue(CurrentCurvePosition) : FVector(1, 1, 1))); 332 | 333 | CurrentCurveValue = UpdatedCurrentData.TranslationData.bOrientToVelocity ? 334 | ORIENT_TO_VELOCITY(CurrentCurveValue) : OwnedMesh->GetComponentRotation().RotateVector(CurrentCurveValue); 335 | 336 | FVector Offset = UpdatedCurrentData.TranslationData.Offset * UpdatedCurrentData.TranslationData.TranslationFactor; 337 | Offset = UpdatedCurrentData.TranslationData.bOrientToVelocity ? 338 | ORIENT_TO_VELOCITY(Offset) : OwnedMesh->GetComponentRotation().RotateVector(Offset); 339 | 340 | CurrentEffectorLocation = Effector.CurrentEffectorLocation; 341 | 342 | 343 | IdealEffectorLocation = UpdatedCurrentData.TranslationData.bAdaptToGroundLevel ? Effector.GroundLocation : Effector.IdealEffectorLocation; 344 | //IdealEffectorLocation = UpdatedCurrentData.TranslationData.bAdaptToGroundLevel ? Effector.IdealEffectorLocation + (Effector.IdealEffectorLocation - Effector.GroundLocation) : Effector.IdealEffectorLocation; 345 | NewCurrentLocation = IdealEffectorLocation + Offset + CurrentCurveValue; 346 | Treshold = UpdatedCurrentData.CorrectionData.DistanceTresholdToAdjust; 347 | bForceSwing = Effector.bForceSwing; 348 | 349 | /*if (UpdatedCurrentData.TranslationData.bAdaptToGroundLevel) 350 | { 351 | NewCurrentLocation = Effector.IdealEffectorLocation.Z - Effector.GroundLocation.Z; 352 | 353 | }*/ 354 | 355 | 356 | Execute_UpdateEffectorTranslation(this, Key, NewCurrentLocation, lerpSpeed > 0/*!bLastFrameWasDisable*/, lerpSpeed); 357 | 358 | Effector.CurrentEffectorLocation = NewCurrentLocation; 359 | } 360 | } 361 | // Step 2.3: If the effector is in 'Stance'. 362 | else 363 | { 364 | if (Effector.bForceSwing) 365 | { 366 | Effector.BlockTime = UpdatedCurrentData.EndSwing >= 0.99f ? 0.f : UpdatedCurrentData.EndSwing; 367 | } 368 | else 369 | { 370 | Effector.BlockTime = -1.f; 371 | 372 | // check for foot ik. 373 | if (/*!bLastFrameWasDisable &&*/ World && !Effector.bCorrectionIK && UpdatedCurrentData.CorrectionData.bComputeCollision) 374 | { 375 | //const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 376 | if (LODSetting.bCanComputeCollisionCorrection) 377 | { 378 | NOBUNANIM_SCOPE_COUNTER(Gait_Stance_IKCorrection); 379 | 380 | // Get origin for IK 381 | FVector Origin; 382 | if (UpdatedCurrentData.CorrectionData.bUseCurrentEffector) 383 | { 384 | Origin = Effector.CurrentEffectorLocation; 385 | } 386 | else 387 | { 388 | Origin = UpdatedCurrentData.CorrectionData.OriginCollisionSocketName.IsNone() ? 389 | OwnedMesh->GetSocketLocation(Key) : 390 | OwnedMesh->GetSocketLocation(UpdatedCurrentData.CorrectionData.OriginCollisionSocketName); 391 | } 392 | FVector Dir = UpdatedCurrentData.CorrectionData.bOrientToVelocity ? ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.AbsoluteDirection) : UpdatedCurrentData.CorrectionData.AbsoluteDirection; 393 | 394 | // Get Dest 395 | FVector Dest = Origin + Dir; 396 | 397 | // Add inverse absolute direction 398 | Origin -= Dir; 399 | 400 | FCollisionQueryParams SweepParam(*GetOwningActor()->GetName(), false, GetOwningActor()); 401 | TArray HitResults; 402 | bool bFoundHit = TraceRay(World, HitResults, Origin, Dest, UpdatedCurrentData.CorrectionData.TraceChannel, SPHERECAST_IK_CORRECTION_RADIUS); 403 | 404 | 405 | if (bFoundHit) 406 | { 407 | 408 | FHitResult& HitResult = GetBestHitResult(HitResults, Origin); 409 | 410 | { 411 | #if WITH_EDITOR 412 | if (HitResult.bBlockingHit && LODSetting.Debug.bShowCollisionCorrection) 413 | { 414 | if (HitResult.bBlockingHit) 415 | { 416 | DrawDebugPoint(World, HitResult.ImpactPoint + ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.CollisionSnapOffset), 10.f, FColor::Red, false, 3.f); 417 | } 418 | } 419 | #endif 420 | } 421 | 422 | // if hit ground 423 | if (HitResult.bBlockingHit) 424 | { 425 | Effector.CurrentEffectorLocation = HitResult.ImpactPoint + ORIENT_TO_VELOCITY(UpdatedCurrentData.CorrectionData.CollisionSnapOffset); 426 | Effector.bCorrectionIK = true; 427 | if (UpdatedCurrentData.EventData.bRaiseOnCollisionEvent) 428 | { 429 | OnCollisionEvent.Broadcast(Key, Effector.CurrentEffectorLocation); 430 | } 431 | } 432 | } 433 | } 434 | } 435 | 436 | Execute_UpdateEffectorTranslation(this, Key, Effector.CurrentEffectorLocation, UpdatedCurrentData.TranslationData.LerpSpeed > 0/*!bLastFrameWasDisable*/, UpdatedCurrentData.TranslationData.LerpSpeed); 437 | } 438 | 439 | bForceSwing = Effector.bForceSwing = false; 440 | //Effector.BeginForceSwingInterval = CurrentAsset.GaitSwingValues[Key].BeginSwing; 441 | //Effector.EndForceSwingInterval = CurrentAsset.GaitSwingValues[Key].EndSwing; 442 | 443 | CurrentEffectorLocation = Effector.CurrentEffectorLocation; 444 | IdealEffectorLocation = Effector.IdealEffectorLocation; 445 | NewCurrentLocation = CurrentEffectorLocation;// Effector.IdealEffectorLocation + TranslationData.Offset + CurrentCurveValue; 446 | Treshold = UpdatedCurrentData.CorrectionData.DistanceTresholdToAdjust; 447 | 448 | bool bUseParentData = UpdatedCurrentAsset.GaitSwingValues.Contains(UpdatedCurrentData.SwingTime.ParentEffector) && Effectors.Contains(UpdatedCurrentData.SwingTime.ParentEffector); 449 | const FGaitCorrectionData& CurrentCorrectionData = UpdatedCurrentAsset.GaitSwingValues.Contains(UpdatedCurrentData.SwingTime.ParentEffector) ? UpdatedCurrentAsset.GaitSwingValues[Key].CorrectionData : UpdatedCurrentData.CorrectionData; 450 | // Step 2.3.1: Check if the effector need to be adjusted 451 | if (CurrentCorrectionData.bAutoAdjustWithIdealEffector) 452 | { 453 | float VectorLength = (Effector.CurrentEffectorLocation - Effector.IdealEffectorLocation).Size2D(); 454 | 455 | // Is the distance breaking treshold? 456 | if ((bUseParentData && Effectors[UpdatedCurrentData.SwingTime.ParentEffector].bForceSwing) || VectorLength >= UpdatedCurrentData.CorrectionData.DistanceTresholdToAdjust) 457 | { 458 | // Here we compute the new interval of swing. 459 | float Begin =/* CurrentAsset.GaitSwingValues[Key].*/BeginSwing; 460 | float End = /*CurrentAsset.GaitSwingValues[Key].*/EndSwing; 461 | 462 | float Alpha = 0.f; 463 | if (Begin > End) 464 | { 465 | Alpha = (1.f - Begin) + End; 466 | } 467 | else 468 | { 469 | Alpha = End - Begin; 470 | } 471 | 472 | float Beta = CurrentTime + Alpha; 473 | // if the end swing is > 1 (absolute time) we just consider that the swing will end the next cycle. 474 | if (Beta >= 1.f) 475 | { 476 | Beta -= 1.f; 477 | } 478 | 479 | Effector.BeginForceSwingInterval = CurrentTime; 480 | Effector.EndForceSwingInterval = Beta; 481 | bForceSwing = Effector.bForceSwing = true; 482 | Effector.BlockTime = -1.f; 483 | } 484 | } 485 | 486 | 487 | } 488 | 489 | #if WITH_EDITOR 490 | // Step 3: Draw Debug. 491 | { 492 | DrawGaitDebug(NewCurrentLocation, IdealEffectorLocation, CurrentEffectorLocation, Treshold, 493 | UpdatedCurrentData.CorrectionData.bAutoAdjustWithIdealEffector, bForceSwing, 494 | &UpdatedCurrentData.DebugData); 495 | } 496 | #endif 497 | } 498 | } 499 | } 500 | } 501 | 502 | // Step 3: if all effector end blend out swtich gait 503 | if (bAllEffectorBlendOutEnd == true) 504 | { 505 | CurrentGaitMode = PendingGaitMode; 506 | PendingGaitMode = ""; 507 | } 508 | } 509 | else 510 | { 511 | CurrentTime = 0; 512 | //Execute_SetProceduralGaitEnable(this, false); 513 | } 514 | 515 | } 516 | else 517 | { 518 | CurrentTime = 0; 519 | //Execute_SetProceduralGaitEnable(this, false); 520 | } 521 | 522 | 523 | #if WITH_EDITOR 524 | UpdateLOD(); 525 | #else 526 | UpdateLOD(); 527 | #endif 528 | } 529 | 530 | void UProceduralGaitAnimInstance::UpdateEffectorTranslation_Implementation(const FName& TargetBone, FVector Translation, bool bLerp, float LerpSpeed) 531 | { 532 | const FVector* Vec = EffectorsTranslation.Find(TargetBone); 533 | if (!Vec) 534 | { 535 | EffectorsTranslation.Add(TargetBone, Translation); 536 | } 537 | else 538 | { 539 | EffectorsTranslation[TargetBone] = bLerp ? FMath::Lerp(*Vec, Translation, LerpSpeed * DeltaTime) : Translation; 540 | } 541 | } 542 | 543 | void UProceduralGaitAnimInstance::UpdateEffectorRotation_Implementation(const FName& TargetBone, FRotator Rotation, float LerpSpeed) 544 | { 545 | const FRotator* Vec = BonesRotation.Find(TargetBone); 546 | if (!Vec) 547 | { 548 | BonesRotation.Add(TargetBone, Rotation); 549 | } 550 | else 551 | { 552 | BonesRotation[TargetBone] = FMath::Lerp(*Vec, Rotation, LerpSpeed * DeltaTime); 553 | } 554 | } 555 | 556 | void UProceduralGaitAnimInstance::SetProceduralGaitEnable_Implementation(bool bEnable) 557 | { 558 | bGaitActive = bEnable ? 1.f : 0.f; 559 | 560 | SetProceduralGaitUpdateEnable(bGaitActive); 561 | //if (!bGaitActive) 562 | //{ 563 | // // WARNING, clear timer will prevent the procedural gait update!!! 564 | // // GetWorld()->GetTimerManager().ClearTimer(GaitUpdateTimer); 565 | // // GaitActive 566 | //} 567 | } 568 | 569 | #pragma endregion 570 | 571 | 572 | #pragma region TERRAIN PREDICTION UTILITIES 573 | 574 | FRotator UProceduralGaitAnimInstance::GetPlaneRotation(FVector A, FVector B, FVector C, FVector RightVector, FRotator& OutRotation, bool bComputeHalf, bool bDebug) 575 | { 576 | FRotator Rotation; 577 | 578 | FVector AP = bComputeHalf ? (A + B) * 0.5f : A; 579 | FVector BP = bComputeHalf ? (B + C) * 0.5f : B; 580 | FVector CP = bComputeHalf ? (C + A) * 0.5f : C; 581 | 582 | FVector AB = BP - AP; 583 | FVector AC = CP - AP; 584 | FVector CrossABC = AB ^ AC; 585 | CrossABC.Normalize(); 586 | 587 | const FVector FloorZAxis = CrossABC; 588 | const FVector FloorXAxis = RightVector ^ FloorZAxis; 589 | const FVector FloorYAxis = FloorZAxis ^ FloorXAxis; 590 | 591 | float OutSlopePitchDegreeAngle = 90.f - FMath::RadiansToDegrees(FMath::Acos(FloorXAxis | FVector(0, 0, 1.f))); 592 | float OutSlopeRollDegreeAngle = 90.f - FMath::RadiansToDegrees(FMath::Acos(FloorYAxis | FVector(0, 0, 1.f))); 593 | 594 | OutRotation.Pitch = 0; 595 | OutRotation.Roll += OutSlopeRollDegreeAngle * 0.25f; 596 | OutRotation.Yaw += OutSlopePitchDegreeAngle * 0.25f; 597 | 598 | #if WITH_EDITOR 599 | if (bDebug) 600 | { 601 | //DEBUG_LOG_FORMAT(Log, "\t=> Rotation: %s", *OutRotation.ToString()); 602 | DrawDebugLine(GetWorld(), AP, BP, FColor(255.f, 255.f, 255.f), false, 0.f, 0, 0.5f); 603 | DrawDebugLine(GetWorld(), BP, CP, FColor(255.f, 255.f, 255.f), false, 0.f, 0, 0.5f); 604 | DrawDebugLine(GetWorld(), CP, AP, FColor(255.f, 255.f, 255.f), false, 0.f, 0, 0.5f); 605 | } 606 | #endif 607 | 608 | return Rotation; 609 | } 610 | 611 | FVector UProceduralGaitAnimInstance::TraceGroundRaycast(UWorld* World, FVector Origin, FVector Dest) 612 | { 613 | const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 614 | 615 | FCollisionObjectQueryParams ObjectQuery(ECollisionChannel::ECC_WorldStatic); 616 | FCollisionQueryParams SweepParam(*this->GetName(), LODSetting.bTraceOnComplex); 617 | FHitResult Hit; 618 | if (!World->LineTraceSingleByObjectType 619 | ( 620 | Hit, 621 | Origin, 622 | Dest, 623 | ObjectQuery, 624 | SweepParam 625 | )) 626 | { 627 | Hit.ImpactPoint = Dest; 628 | } 629 | 630 | if (GroundReflection.bShowDebugPlanes) 631 | { 632 | DrawDebugLine(World, Origin, Dest, FColor::Cyan, false, 0.f, 0, 0.5f); 633 | DrawDebugPoint(World, Hit.ImpactPoint, 10, FColor::Cyan, false, 0.f); 634 | } 635 | return Hit.ImpactPoint; 636 | } 637 | 638 | FRotator UProceduralGaitAnimInstance::ComputeGroundReflection_LOD0() 639 | { 640 | // Get Socket location 641 | FVector F = OwnedMesh->GetSocketLocation(GroundReflection.FrontSocket); 642 | FVector B = OwnedMesh->GetSocketLocation(GroundReflection.BackSocket); 643 | FVector R = OwnedMesh->GetSocketLocation(GroundReflection.RightSocket); 644 | FVector L = OwnedMesh->GetSocketLocation(GroundReflection.LeftSocket); 645 | 646 | // Compute centroid 647 | TArray Average; 648 | Average.Add(F); 649 | Average.Add(B); 650 | Average.Add(R); 651 | Average.Add(L); 652 | FVector C = GetAverage(Average); 653 | 654 | // Trace for ground 655 | F = TraceGroundRaycast(GetWorld(), F, F + RayVector); 656 | B = TraceGroundRaycast(GetWorld(), B, B + RayVector); 657 | R = TraceGroundRaycast(GetWorld(), R, R + RayVector); 658 | L = TraceGroundRaycast(GetWorld(), L, L + RayVector); 659 | C = TraceGroundRaycast(GetWorld(), C, C + RayVector); 660 | 661 | // Compute ground reflection 662 | FRotator Rotation(0, 0, 0); 663 | FVector RightVec = OwnedMesh->GetRightVector(); 664 | GetPlaneRotation(F, R, C, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 665 | GetPlaneRotation(C, R, B, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 666 | GetPlaneRotation(C, B, L, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 667 | GetPlaneRotation(F, C, L, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 668 | 669 | return Rotation; 670 | } 671 | 672 | FRotator UProceduralGaitAnimInstance::ComputeGroundReflection_LOD1() 673 | { 674 | FVector Front = OwnedMesh->GetSocketLocation(GroundReflection.FrontSocket); 675 | Front = TraceGroundRaycast(GetWorld(), Front, Front + RayVector); 676 | FVector Back = OwnedMesh->GetSocketLocation(GroundReflection.BackSocket); 677 | Back = TraceGroundRaycast(GetWorld(), Back, Back + RayVector); 678 | FVector Right = OwnedMesh->GetSocketLocation(GroundReflection.RightSocket); 679 | Right = TraceGroundRaycast(GetWorld(), Right, Right + RayVector); 680 | FVector Left = OwnedMesh->GetSocketLocation(GroundReflection.LeftSocket); 681 | Left = TraceGroundRaycast(GetWorld(), Left, Left + RayVector); 682 | 683 | FVector RightVec = OwnedMesh->GetRightVector(); 684 | 685 | FRotator Rotation(0, 0, 0); 686 | GetPlaneRotation(Front, Right, Back, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 687 | GetPlaneRotation(Front, Back, Left, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 688 | GetPlaneRotation(Front, Right, Left, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 689 | GetPlaneRotation(Right, Back, Left, RightVec, Rotation, GroundReflection.bUseHalfVector, GroundReflection.bShowDebugPlanes); 690 | 691 | return Rotation; 692 | } 693 | 694 | #pragma endregion 695 | 696 | 697 | #pragma region PROCEDURAL GAIT UTILITIES 698 | bool UProceduralGaitAnimInstance::TraceRay(UWorld* World, TArray& HitResults, FVector Origin, FVector Dest, TEnumAsByte TraceChannel, float SphereCastRadius) 699 | { 700 | const FProceduralGaitLODSettings& LODSetting = UNobunanimSettings::GetLODSetting(CurrentLOD); 701 | 702 | // if correction Level0 then zero computation. 703 | if (LODSetting.CorrectionLevel == ENobunanimIKCorrectionLevel::IKL_Level0) 704 | { 705 | return false; 706 | } 707 | 708 | FCollisionQueryParams SweepParam(*GetOwningActor()->GetName(), LODSetting.bTraceOnComplex, GetOwningActor()); 709 | 710 | bool bFoundHit = World->LineTraceMultiByChannel 711 | ( 712 | HitResults, 713 | Origin, 714 | Dest, 715 | TraceChannel, 716 | SweepParam, 717 | FCollisionResponseParams::DefaultResponseParam 718 | ); 719 | 720 | #if WITH_EDITOR 721 | if (LODSetting.Debug.bShowCollisionCorrection) 722 | { 723 | DrawDebugDirectionalArrow(World, Origin, Dest, 5, LODSetting.Debug.IKTraceColor, false, LODSetting.Debug.IKTraceDuration, 0, 0.5f); 724 | DrawDebugPoint(World, Dest, 10, LODSetting.Debug.LODColor, false, LODSetting.Debug.IKTraceDuration); 725 | } 726 | #endif 727 | 728 | if (LODSetting.CorrectionLevel == ENobunanimIKCorrectionLevel::IKL_Level1) 729 | { 730 | return bFoundHit; 731 | } 732 | 733 | if (!bFoundHit) 734 | { 735 | bFoundHit = World->SweepMultiByChannel 736 | ( 737 | HitResults, 738 | Origin, 739 | Dest, 740 | FQuat::Identity, 741 | TraceChannel, 742 | FCollisionShape::MakeSphere(SphereCastRadius), 743 | SweepParam, 744 | FCollisionResponseParams::DefaultResponseParam 745 | ); 746 | 747 | #if WITH_EDITOR 748 | if (LODSetting.Debug.bShowCollisionCorrection) 749 | { 750 | DrawDebugCapsule(World, (Dest + Origin) * 0.5f, ((Dest - Origin)).Size()* 0.5f, SphereCastRadius, FQuat((Origin - Dest).GetUnsafeNormal().Rotation()), LODSetting.Debug.LODColor, false, LODSetting.Debug.IKTraceDuration, 0, .5f); 751 | } 752 | #endif 753 | } 754 | 755 | return bFoundHit; 756 | } 757 | 758 | 759 | FHitResult& UProceduralGaitAnimInstance::GetBestHitResult(TArray& HitResults, FVector IdealLocation) 760 | { 761 | FHitResult& HitResult = HitResults[0]; 762 | 763 | float BestDistance = 9999999.f; 764 | for (int i = 0, n = HitResults.Num(); i < n; ++i) 765 | { 766 | FHitResult& CurrentHit = HitResults[i]; 767 | 768 | float Temp = FMath::Abs(((IdealLocation) - CurrentHit.ImpactPoint).SizeSquared()); 769 | if (Temp < BestDistance && HitResult.bBlockingHit) 770 | { 771 | BestDistance = Temp; 772 | HitResult = CurrentHit; 773 | } 774 | } 775 | 776 | return HitResult; 777 | } 778 | 779 | 780 | void UProceduralGaitAnimInstance::UpdateGaitMode_Implementation(const FName& NewGaitName) 781 | { 782 | if (GaitsData.Contains(NewGaitName)) 783 | { 784 | if (CurrentGaitMode != NewGaitName && PendingGaitMode != NewGaitName) 785 | { 786 | if (CurrentGaitMode.IsNone() || CurrentGaitMode == "None") 787 | { 788 | CurrentGaitMode = NewGaitName; 789 | 790 | } 791 | else 792 | { 793 | PendingGaitMode = NewGaitName; 794 | } 795 | 796 | Execute_SetProceduralGaitEnable(this, true); 797 | SetProceduralGaitUpdateEnable(true); 798 | //CurrentGaitMode = NewGaitName; 799 | //SetComponentTickEnabled(true); 800 | } 801 | } 802 | else 803 | { 804 | SetProceduralGaitUpdateEnable(false); 805 | Execute_SetProceduralGaitEnable(this, false); 806 | //SetComponentTickEnabled(false); 807 | //DEBUG_LOG_FORMAT(Warning, "Invalid NewGaitName %s. There is no gait data corresponding. Ignored.", NewGaitName); 808 | } 809 | } 810 | 811 | 812 | void UProceduralGaitAnimInstance::UpdateEffectors(const UGaitDataAsset& CurrentAsset) 813 | { 814 | NOBUNANIM_SCOPE_COUNTER(Gait_UpdateEffectors); 815 | 816 | TArray SwingValuesKeys; 817 | const TMap& SwingValues = CurrentAsset.GaitSwingValues;//GaitsData[Keys[i]]->GaitSwingValues; 818 | SwingValues.GetKeys(SwingValuesKeys); 819 | 820 | for (int j = 0, m = SwingValuesKeys.Num(); j < m; ++j) 821 | { 822 | NOBUNANIM_SCOPE_COUNTER(Gait_UpdateEffector_One); 823 | 824 | FName Key = SwingValuesKeys[j]; 825 | FVector EffectorLocation = OwnedMesh->GetSocketTransform(Key, CurrentAsset.GaitSwingValues[Key].TranslationData.TransformSpace.GetValue()).GetLocation(); 826 | 827 | if (Effectors.Contains(Key)) 828 | { 829 | Effectors[Key].IdealEffectorLocation = EffectorLocation; 830 | 831 | if (CurrentAsset.GaitSwingValues[Key].TranslationData.bAdaptToGroundLevel) 832 | { 833 | FVector Dir = FVector::UpVector; 834 | FVector GroundLocation; 835 | TArray HitResults; 836 | FVector GroundReferenceLocation = OwnedMesh->GetSocketLocation(CurrentAsset.GaitSwingValues[Key].TranslationData.GroundReferenceSocket); 837 | bool bFound = TraceRay(GetWorld(), HitResults, EffectorLocation, FVector(EffectorLocation.X, EffectorLocation.Y, GroundReferenceLocation.Z), ECollisionChannel::ECC_Visibility, SPHERECAST_IK_CORRECTION_RADIUS); 838 | if (!bFound) 839 | { 840 | //GroundLocation = EffectorLocation + RayVector; 841 | Effectors[Key].GroundLocation = EffectorLocation; 842 | } 843 | else 844 | { 845 | FHitResult& HitResult = GetBestHitResult(HitResults, EffectorLocation); 846 | if (HitResult.bBlockingHit) 847 | { 848 | GroundLocation = HitResult.ImpactPoint; 849 | Dir = HitResult.Normal; 850 | 851 | if (GroundLocation.Z > GroundReferenceLocation.Z) 852 | { 853 | FVector Correction = Dir * (GroundLocation.Z - GroundReferenceLocation.Z); 854 | Effectors[Key].GroundLocation = EffectorLocation + Correction; 855 | } 856 | else 857 | { 858 | Effectors[Key].GroundLocation = EffectorLocation; 859 | } 860 | } 861 | else 862 | { 863 | Effectors[Key].GroundLocation = EffectorLocation; 864 | //GroundLocation = EffectorLocation + RayVector; 865 | } 866 | } 867 | 868 | //Effectors[Key].GroundLocation = GroundLocation; 869 | //Effectors[Key].GroundLocation = EffectorLocation;// +(Dir * (Effectors[Key].IdealEffectorLocation.Z - GroundLocation.Z)); 870 | //Effectors[Key].GroundLocation.X = Effectors[Key].GroundLocation.Y = 0.f; 871 | } 872 | 873 | /*if (bLastFrameWasDisable) 874 | { 875 | Effectors[Key].CurrentEffectorLocation = EffectorLocation; 876 | Execute_UpdateEffectorTranslation(this, Key, EffectorLocation, false, 0); 877 | }*/ 878 | } 879 | else 880 | { 881 | FGaitEffectorData Effector; 882 | Effector.IdealEffectorLocation = EffectorLocation; 883 | Effectors.Add(Key, Effector); 884 | } 885 | } 886 | } 887 | 888 | 889 | bool UProceduralGaitAnimInstance::IsInRange(float Value, float Min, float Max, float& OutRangeMin, float& OutRangeMax) 890 | { 891 | bool A = Value >= Min && Value <= Max; 892 | bool APrime = Value >= Max; 893 | 894 | // Gave me headache ._. 895 | OutRangeMin = A ? Min : (APrime ? Min : Min - 1.f); 896 | OutRangeMax = A ? Max : (APrime ? 1.f /*- Min + Min*/ + Max : Max); 897 | 898 | if (A) 899 | { 900 | return true; 901 | } 902 | else 903 | { 904 | bool B1 = Value <= Min && Value <= Max; 905 | bool B2 = Value >= Min && Value >= Max; 906 | bool B3 = B1 || B2; 907 | bool C = Min >= Max && B3; 908 | 909 | return C; 910 | } 911 | } 912 | 913 | void UProceduralGaitAnimInstance::DrawGaitDebug(FVector Position, FVector EffectorLocation, FVector CurrentLocation, float Treshold, bool bAutoAdjustWithIdealEffector, bool bForceSwing, const FGaitDebugData* DebugData) 914 | { 915 | NOBUNANIM_SCOPE_COUNTER(ProceduralGaitAnimInstance__DrawGaitDebug); 916 | 917 | if (bShowDebug && DebugData->bDrawDebug) 918 | { 919 | UWorld* World = GetWorld(); 920 | if (World) 921 | { 922 | DrawDebugPoint(World, Position, 2, FColor::Yellow, false, 1.f); 923 | DrawDebugSphere(World, Position, 2, 12, FColor(255.f, 130.f, 0.f), false, 0.f); 924 | DrawDebugSphere(World, EffectorLocation, 2, 12, FColor(255.f, 0.f, 0.f), false, 0.f); 925 | 926 | DrawDebugLine(World, EffectorLocation, Position, FColor(255.f, 255.f, 255.f), false, 0.f, 0, 0.5f); 927 | //DrawDebugLine(World, CurrentLocation, EffectorLocation, FColor(255.f, 0.f, 0.f), false, 0.f, 0, 1.f); 928 | 929 | FRotator Rot = LastVelocity.Rotation(); 930 | DrawDebugDirectionalArrow(World, EffectorLocation, EffectorLocation + (Rot.RotateVector(FVector::ForwardVector) * 10.f), 1, DebugData->VelocityColor, false, 0, 0, 1.f); 931 | 932 | if (bAutoAdjustWithIdealEffector) 933 | { 934 | DrawDebugCircle(World, FVector(CurrentLocation.X, CurrentLocation.Y, EffectorLocation.Z), Treshold, 20, bForceSwing ? DebugData->ForceSwingColor : DebugData->CorrectionCircleColor, false, 0.f, 0, bForceSwing ? 1.f : 0.75f, FVector(0, 1, 0), FVector(1, 0, 0), false); 935 | } 936 | } 937 | } 938 | 939 | #if WITH_EDITOR 940 | if (UNobunanimSettings::GetLODSetting(CurrentLOD).Debug.bShowLOD 941 | || bShowLOD 942 | ) 943 | { 944 | UWorld* World = GetWorld(); 945 | if (World) 946 | { 947 | DrawDebugSolidBox(World, GetOwningActor()->GetActorLocation(), GetOwningActor()->GetSimpleCollisionCylinderExtent(), UNobunanimSettings::GetLODSetting(CurrentLOD).Debug.LODColor, false, 0.f); 948 | } 949 | } 950 | #endif 951 | } 952 | 953 | void UProceduralGaitAnimInstance::UpdateLOD(bool bForceUpdate) 954 | { 955 | if (CurrentLOD != OwnedMesh->PredictedLODLevel 956 | || bForceUpdate) 957 | { 958 | CurrentLOD = OwnedMesh->PredictedLODLevel; 959 | 960 | // If procedural gait update is running, hard reset to adapt framerate. 961 | if (bUpdateGaitActive) 962 | { 963 | float Delay = 1.f / (float)UNobunanimSettings::GetLODSetting(CurrentLOD = OwnedMesh->PredictedLODLevel).TargetFPS; 964 | // Set timer will auto-clear if needed. 965 | GetWorld()->GetTimerManager().SetTimer(GaitUpdateTimer, [this]() { ProceduralGaitUpdate(); }, Delay, true, false); 966 | } 967 | } 968 | } 969 | 970 | void UProceduralGaitAnimInstance::SetProceduralGaitUpdateEnable(bool bEnable) 971 | { 972 | UWorld* World = GetWorld(); 973 | if (!World || !World->IsGameWorld()) 974 | { 975 | return; 976 | } 977 | 978 | if (bEnable && !bUpdateGaitActive) 979 | { 980 | bUpdateGaitActive = true; 981 | float Delay = 1.f / (float)UNobunanimSettings::GetLODSetting(CurrentLOD = OwnedMesh->PredictedLODLevel).TargetFPS; 982 | // Set timer will auto-clear if needed. 983 | World->GetTimerManager().SetTimer(GaitUpdateTimer, [this]() { ProceduralGaitUpdate(); }, Delay, true, false); 984 | } 985 | else if(!bEnable && bUpdateGaitActive) 986 | { 987 | bUpdateGaitActive = false; 988 | 989 | World->GetTimerManager().ClearTimer(GaitUpdateTimer); 990 | } 991 | } 992 | 993 | #pragma endregion --------------------------------------------------------------------------------