Improved Docs, AnimationStepApplyMesh+Materials, some other minor tweaks

This commit is contained in:
2026-02-06 17:34:26 -05:00
parent a2e179e15b
commit 56765fdc16
28 changed files with 731 additions and 165 deletions

View File

@@ -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());
}
}