Improved Docs, AnimationStepApplyMesh+Materials, some other minor tweaks
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
#include "AnimQueue.h"
|
||||
#include "UtilityLibrary.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/MeshComponent.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "AssetLookup.h"
|
||||
#include "LuprexGameModeBase.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include <iostream>
|
||||
|
||||
FlxAnimationStep::FlxAnimationStep(int64 hash, std::string_view body) {
|
||||
@@ -535,3 +541,132 @@ FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void UlxAnimationStepLibrary::AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor) {
|
||||
if (actor == nullptr) return;
|
||||
|
||||
// Step 1: Build tables of mat_ parameters (vectors and scalars).
|
||||
//
|
||||
TMap<FName, FVector> VectorParams;
|
||||
TMap<FName, float> ScalarParams;
|
||||
{
|
||||
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
||||
FlxAnimationStepDecoder decoder(body);
|
||||
while (!decoder.AtEOF()) {
|
||||
FlxAnimationField field = decoder.ReadField();
|
||||
if (field.Name.size() <= 4) continue;
|
||||
if (field.Name.substr(0, 4) != "mat_") continue;
|
||||
std::string_view suffix = field.Name.substr(4);
|
||||
FName paramName(suffix.size(), (const UTF8CHAR*)suffix.data());
|
||||
if (field.Type == SimpleDynamicTag::VECTOR) {
|
||||
VectorParams.Add(paramName, FVector(field.X, field.Y, field.Z));
|
||||
} else if (field.Type == SimpleDynamicTag::NUMBER) {
|
||||
ScalarParams.Add(paramName, (float)field.X);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Early exit if no mat_ parameters found.
|
||||
//
|
||||
if (VectorParams.IsEmpty() && ScalarParams.IsEmpty()) return;
|
||||
|
||||
// Step 3: Loop over all mesh components and apply material parameters.
|
||||
//
|
||||
TInlineComponentArray<UMeshComponent*> MeshComponents;
|
||||
actor->GetComponents<UMeshComponent>(MeshComponents);
|
||||
|
||||
for (UMeshComponent* MeshComp : MeshComponents) {
|
||||
int32 NumMaterials = MeshComp->GetNumMaterials();
|
||||
for (int32 SlotIndex = 0; SlotIndex < NumMaterials; SlotIndex++) {
|
||||
UMaterialInterface* CurrentMat = MeshComp->GetMaterial(SlotIndex);
|
||||
if (CurrentMat == nullptr) continue;
|
||||
|
||||
// Check if the material has any parameter that doesn't
|
||||
// match what was specified.
|
||||
//
|
||||
bool AnyMismatch = false;
|
||||
for (auto& Pair : VectorParams) {
|
||||
FLinearColor Compare;
|
||||
if (CurrentMat->GetVectorParameterValue(Pair.Key, Compare)) {
|
||||
FLinearColor Desired(Pair.Value.X, Pair.Value.Y, Pair.Value.Z);
|
||||
if (Compare != Desired) {
|
||||
AnyMismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& Pair : ScalarParams) {
|
||||
float Compare;
|
||||
if (CurrentMat->GetScalarParameterValue(Pair.Key, Compare)) {
|
||||
if (Compare != Pair.Value) {
|
||||
AnyMismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!AnyMismatch) continue;
|
||||
|
||||
// Strip away any existing dynamic material before creating a new one.
|
||||
//
|
||||
UMaterialInterface* BaseMat = CurrentMat;
|
||||
while (UMaterialInstanceDynamic* MID = Cast<UMaterialInstanceDynamic>(BaseMat)) {
|
||||
BaseMat = MID->Parent;
|
||||
}
|
||||
if (BaseMat != CurrentMat) {
|
||||
MeshComp->SetMaterial(SlotIndex, BaseMat);
|
||||
}
|
||||
|
||||
// Create the new dynamic material.
|
||||
//
|
||||
UMaterialInstanceDynamic* MID = MeshComp->CreateDynamicMaterialInstance(SlotIndex);
|
||||
for (auto& Pair : VectorParams) {
|
||||
FVector& Vec = Pair.Value;
|
||||
MID->SetVectorParameterValue(Pair.Key, FLinearColor(Vec.X, Vec.Y, Vec.Z));
|
||||
}
|
||||
for (auto& Pair : ScalarParams) {
|
||||
MID->SetScalarParameterValue(Pair.Key, Pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UlxAnimationStepLibrary::AnimationStepApplyMesh(const FlxAnimationStep& step, bool FallbackToBP, AActor* actor) {
|
||||
if (actor == nullptr) return;
|
||||
|
||||
// Step 1: Look for a "mesh" or "bp" string field in the animation step.
|
||||
//
|
||||
FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
|
||||
if (MeshName.IsEmpty() && FallbackToBP) {
|
||||
MeshName = AnimationStepGetString(step, TEXT("bp"));
|
||||
}
|
||||
if (MeshName.IsEmpty()) return;
|
||||
|
||||
// Step 2: Find the actor's mesh component. There must be exactly one.
|
||||
//
|
||||
TInlineComponentArray<UMeshComponent*> MeshComponents;
|
||||
actor->GetComponents<UMeshComponent>(MeshComponents);
|
||||
if (MeshComponents.Num() != 1) {
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("AnimationStepApplyMesh: Actor %s has %d mesh components, expected exactly 1"), *actor->GetName(), MeshComponents.Num());
|
||||
return;
|
||||
}
|
||||
UMeshComponent* MeshComp = MeshComponents[0];
|
||||
|
||||
// Step 3: Apply the mesh based on the component type.
|
||||
//
|
||||
if (UStaticMeshComponent* StaticComp = Cast<UStaticMeshComponent>(MeshComp)) {
|
||||
UStaticMesh* NewMesh = nullptr;
|
||||
UlxAssetLookup::LoadStaticMeshAsset(NewMesh, actor, MeshName, false);
|
||||
if (NewMesh == nullptr) return;
|
||||
if (StaticComp->GetStaticMesh() != NewMesh) {
|
||||
StaticComp->SetStaticMesh(NewMesh);
|
||||
}
|
||||
} else if (USkeletalMeshComponent* SkelComp = Cast<USkeletalMeshComponent>(MeshComp)) {
|
||||
// TODO: Skeletal mesh support
|
||||
// USkeletalMesh* NewMesh = nullptr;
|
||||
// UlxAssetLookup::LoadSkeletalMeshAsset(NewMesh, actor, MeshName, true);
|
||||
// if (NewMesh == nullptr) return;
|
||||
// if (SkelComp->GetSkeletalMeshAsset() != NewMesh) {
|
||||
// SkelComp->SetSkeletalMeshAsset(NewMesh);
|
||||
// }
|
||||
} else {
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("AnimationStepApplyMesh: Actor %s has unsupported mesh component type"), *actor->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,22 @@ public:
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintAutocast), Category = "Luprex|Animation Step")
|
||||
static int64 AnimationStepID(const FlxAnimationStep& step) { return step.Hash; }
|
||||
|
||||
// Scan an animation step for key-value pairs of the form mat_XXXX={x,y,z}.
|
||||
// For each match, create a dynamic material instance on the actor's mesh
|
||||
// components and set the vector parameter XXXX. Materials are restored to
|
||||
// their base (non-dynamic) state before applying, so parameters from
|
||||
// previous calls do not persist.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
|
||||
static void AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor);
|
||||
|
||||
// Look for a mesh=name key-value pair in the animation step.
|
||||
// If found, load the named mesh and apply it to the actor's
|
||||
// mesh component. The actor must have exactly one mesh component.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
|
||||
static void AnimationStepApplyMesh(const FlxAnimationStep& step, bool FallbackToBP, AActor* actor);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user