UE Foliage工具拓展

项目从4.24升级到4.26后 foliage的editor ui布局发生了很大的变化 顺带原项目中加的功能也要简单的移植下。

1.Fix:

UE的植被有个Invalid的标志,即Fix左边的这个小图标,这个是ue的自带功能选出所有的不合法的植被。

合法条件为: FoliageInstance的BaseComponent是否能在规定范围(射线检测)内找到,且误差在5cm内,且附着的BaseComponent与FoliageInstance是否在同一个level中(项目中新加)。

通俗的理解 所谓的不合法是指1.该植被没有附着的地面 2.地面与植被处于不同的level 3.离开地面过高 4.陷入地面过低。

                   1/2会影响关卡拆分移动重组导致植被的位置出错。3/4在表现上不美观。

不合法的植被是怎么来的。1美术同学的操作,比如为了美观移动某个植被到“空中”。2.拆关卡,静态合批拆合批的操作。

 

新加的Fix功能的目的即整体处理上面的4个问题。 具体代码与FoliageEdMode.cpp的SelectInvalidInstances相对应。分成2部分。

part1 

step1 找到离需要修复的FoliageInstance最近的地面

step2  给FoliageInstance一个新的BaseId

step3 设置一个能够满足5cm误差的ZOffset来完成”对冲“

 1 bool FEdModeFoliage::FixInstanceToGround(AInstancedFoliageActor* InIFA, float AlignMaxAngle, FFoliageInfo& Mesh, int32 InstanceIdx)
 2 {
 3     UWorld* InWorld = GetWorld();
 4 
 5     FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(FoliageGroundCheck), true);
 6     QueryParams.bReturnFaceIndex = false;
 7     FCollisionShape SphereShape;
 8     SphereShape.SetSphere(0.f);
 9     TArray<FHitResult> Hits; Hits.Reserve(16);
10     FHitResult HitResult;
11 
12     FFoliageInstance& Instance = Mesh.Instances[InstanceIdx];
13 
14     FVector InstanceTraceRange = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, FOLIAGE_INVALID_TEST_TRACE));
15     FVector Start = Instance.Location + InstanceTraceRange;
16     FVector End = Instance.Location - InstanceTraceRange;
17     
18     InWorld->SweepMultiByObjectType(Hits, Start, End, FQuat::Identity, FCollisionObjectQueryParams(ECC_WorldStatic), SphereShape, QueryParams);
19 
20     // Find nearest hit
21     float TempDistance = InstanceTraceRange.Size();
22     for (const FHitResult& Hit : Hits)
23     {
24         float HitDistance = (Hit.Location - Instance.Location).Size();
25         if (HitDistance < TempDistance)
26         {
27             TempDistance = HitDistance;
28             HitResult = Hit;
29         }
30     }
31 
32     if (HitResult.bBlockingHit)
33     {
34         UPrimitiveComponent* HitComponent = HitResult.Component.Get();
35 
36         // Find BSP brush 
37         UModelComponent* ModelComponent = Cast<UModelComponent>(HitComponent);
38         if (ModelComponent)
39         {
40             ABrush* BrushActor = ModelComponent->GetModel()->FindBrush(HitResult.Location);
41             if (BrushActor)
42             {
43                 HitComponent = BrushActor->GetBrushComponent();
44             }
45         }
46 
47         // Set new base
48         auto NewBaseId = InIFA->InstanceBaseCache.AddInstanceBaseId(Mesh.ShouldAttachToBaseComponent() ? HitComponent : nullptr);
49         Mesh.RemoveFromBaseHash(InstanceIdx);
50         Instance.BaseId = NewBaseId;
51         if (Instance.BaseId == FFoliageInstanceBaseCache::InvalidBaseId)
52         {
53             Instance.BaseComponent = nullptr;
54         }
55         Mesh.AddToBaseHash(InstanceIdx);
56 
57         // Set new ZOffset
58         float InstanceZUpOffsetFixLimitThreshold = CVarOffGroundZUpOffsetThreshold.GetValueOnGameThread();
59         float InstanceZDownOffsetFixLimitThreshold = CVarOffGroundZDownOffsetThreshold.GetValueOnGameThread();
60         float InstanceOffGroundLocalThreshold = CVarOffGroundTreshold.GetValueOnGameThread();
61         float InstanceWorldTreshold = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, InstanceOffGroundLocalThreshold)).Size();
62         float InstanceWorldZUpOffsetFixLimitThreshold = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, InstanceZUpOffsetFixLimitThreshold)).Size();
63         float InstanceWorldZDownOffsetFixLimitThreshold = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, InstanceZDownOffsetFixLimitThreshold)).Size();
64 
65         FVector InstanceWorldZOffset = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, Instance.ZOffset));
66         FVector OffsetToGround = Instance.Location - (HitResult.Location + InstanceWorldZOffset);
67         bool bIsUpOffset = OffsetToGround.Z > 0 ? true : false;
68         float DistanceToGround = OffsetToGround.Size();
69         
70         if (OffsetToGround.Z >= 0)
71         {
72             if (DistanceToGround > InstanceWorldZUpOffsetFixLimitThreshold)
73             {
74                 return false;
75             }
76             if ((DistanceToGround - InstanceWorldTreshold) > KINDA_SMALL_NUMBER)
77             {
78                 check(InstanceWorldZUpOffsetFixLimitThreshold > KINDA_SMALL_NUMBER);
79                 float ScaleRateLocalToWorld = InstanceZUpOffsetFixLimitThreshold / InstanceWorldZUpOffsetFixLimitThreshold;
80                 Instance.ZOffset += DistanceToGround * ScaleRateLocalToWorld;
81             }
82         }
83         else
84         {
85             if (DistanceToGround > InstanceWorldZDownOffsetFixLimitThreshold)
86             {
87                 return false;
88             }
89             if ((DistanceToGround - InstanceWorldTreshold) > KINDA_SMALL_NUMBER)
90             {
91                 check(InstanceWorldZDownOffsetFixLimitThreshold > KINDA_SMALL_NUMBER);
92                 float ScaleRateLocalToWorld = InstanceZDownOffsetFixLimitThreshold / InstanceWorldZDownOffsetFixLimitThreshold;
93                 Instance.ZOffset -= DistanceToGround * ScaleRateLocalToWorld;
94             }
95         }
96         return true;
97     }
98     return false;
99 }
View Code

 

part2 

step1 将植被移到合理的关卡中

 1 void FEdModeFoliage::MoveSelectInstancesToCorrectLevel(UWorld* InWorld)
 2 {
 3     GEditor->BeginTransaction(NSLOCTEXT("UnrealEd", "FoliageMode_Transaction_:MoveSelectInstancesToCorrectLevel", "Move Foliage To Correct Level"));
 4     {
 5 
 6         const int32 NumLevels = InWorld->GetNumLevels();
 7         for (int32 LevelIdx = 0; LevelIdx < NumLevels; ++LevelIdx)
 8         {
 9             ULevel* Level = InWorld->GetLevel(LevelIdx);
10             AInstancedFoliageActor* IFA = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(Level);
11             if (IFA)
12             {
13                 bool bFoundSelection = false;
14 
15                 for (auto& MeshPair : IFA->FoliageInfos)
16                 {
17                     FFoliageInfo& FoliageInfo = *MeshPair.Value;
18 
19                     if (FoliageInfo.SelectedIndices.Array().Num() > 0)
20                     {
21                         // Mark actor once we found selection
22                         if (!bFoundSelection)
23                         {
24                             IFA->Modify();
25                             bFoundSelection = true;
26                         }
27                         int InstanceIndex = 0;
28                         while (InstanceIndex < FoliageInfo.Instances.Num())
29                         {
30                             if (!FixInstanceToCorrectLevel(IFA, MeshPair.Key->AlignMaxAngle, FoliageInfo, InstanceIndex))
31                             {
32                                 InstanceIndex++;
33                             }
34                         }
35                     }
36                 }
37             }
38         }
39     }
40     GEditor->EndTransaction();
41 }
View Code
 1 bool FEdModeFoliage::FixInstanceToCorrectLevel(AInstancedFoliageActor* InIFA, float AlignMaxAngle, FFoliageInfo& Mesh, int32 InstanceIdx)
 2 {
 3     UWorld* InWorld = GetWorld();
 4 
 5     FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(FoliageGroundCheck), true);
 6     QueryParams.bReturnFaceIndex = false;
 7     FCollisionShape SphereShape;
 8     SphereShape.SetSphere(0.f);
 9     TArray<FHitResult> Hits; Hits.Reserve(16);
10     FHitResult HitResult;
11 
12     FFoliageInstance& Instance = Mesh.Instances[InstanceIdx];
13 
14     FVector InstanceTraceRange = Instance.GetInstanceWorldTransform().TransformVector(FVector(0.f, 0.f, FOLIAGE_INVALID_TEST_TRACE));
15     FVector Start = Instance.Location + InstanceTraceRange;
16     FVector End = Instance.Location - InstanceTraceRange;
17 
18     InWorld->SweepMultiByObjectType(Hits, Start, End, FQuat::Identity, FCollisionObjectQueryParams(ECC_WorldStatic), SphereShape, QueryParams);
19 
20     // Find nearest hit
21     float TempDistance = InstanceTraceRange.Size();
22     for (const FHitResult& Hit : Hits)
23     {
24         float HitDistance = (Hit.Location - Instance.Location).Size();
25         if (HitDistance < TempDistance)
26         {
27             TempDistance = HitDistance;
28             HitResult = Hit;
29         }
30     }
31 
32     if (HitResult.bBlockingHit)
33     {
34         UPrimitiveComponent* HitComponent = HitResult.Component.Get();
35 
36         if (HitComponent->GetComponentLevel() != InIFA->GetLevel())
37         {
38             TSet<int32> InInstanceSet;
39             InInstanceSet.Emplace(InstanceIdx);
40             for (auto& Pair : InIFA->FoliageInfos)
41             {
42                 if (&Pair.Value.Get() == &Mesh)
43                 {
44                     UFoliageType* FoliageType = Pair.Key;
45                     InIFA->MoveInstancesToLevel(HitComponent->GetComponentLevel(), InInstanceSet, &Mesh, FoliageType);
46                     return true;
47                 }
48             }
49         }
50     }
51     return false;
52 }
View Code

 

上述操作主要基于射线检测,为了能够直观的看到修复成果可以在select的地方打下log提醒美术同学。

---->

 

剩下的都是植被高度差过于离谱的 建议手动修复。

 

2. Replace

 

 

Replace基于原功能Select+Remove+Paint

该工具的目的是让美术同学能够快速地将原来位置的植被刷成指定植被。

void FEdModeFoliage::ReplaceSelectedInstances(UWorld* InWorld, UFoliageType* FoliageToReplace)
{
    if (FoliageToReplace == nullptr)
        return;
    RemoveSelectedInstances(InWorld);
    ReplaceInstancesAfterRemove(InWorld, FoliageToReplace, 1.0);
}
View Code
void FEdModeFoliage::ReplaceInstancesAfterRemove(UWorld* InWorld, const UFoliageType* Settings, float Pressure)
{
    SCOPE_CYCLE_COUNTER(STAT_FoliageAddInstanceBrush);

    int32 DesiredInstanceCount = RemovedInstanceLocations.Num();
    UWorld* World = GetWorld();
    const bool bHasValidLandscapeLayers = IsLandscapeLayersArrayValid(Settings->LandscapeLayers);

    TArray<int32> ExistingInstanceBuckets;
    ExistingInstanceBuckets.AddZeroed(NUM_INSTANCE_BUCKETS);

    if (DesiredInstanceCount > 0)
    {
        TArray<FDesiredFoliageInstance> DesiredInstances;    //we compute instances for the brush
        DesiredInstances.Reserve(DesiredInstanceCount);

        for (int32 DesiredIdx = 0; DesiredIdx < DesiredInstanceCount; DesiredIdx++)
        {
            FVector Start = RemovedInstanceLocations[DesiredIdx] + FVector::UpVector * FOLIAGE_INVALID_TEST_TRACE;
            FVector End = RemovedInstanceLocations[DesiredIdx] - FVector::UpVector * FOLIAGE_INVALID_TEST_TRACE;
            FDesiredFoliageInstance* DesiredInstance = new (DesiredInstances)FDesiredFoliageInstance(Start, End);
        }
        AddInstancesImp(InWorld, Settings, DesiredInstances, ExistingInstanceBuckets, Pressure, &LandscapeLayerCaches, &UISettings, nullptr, true);
    }
    PopulateFoliageMeshList();
}
View Code

然后加上对应的植被给予选择(下面是slate以及editor的拓展代码 比较公式化 不熟悉看起来会有点吃力)

 FoliageEditorObject.h

/**
* Utility used for Foliage Edition
*/

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "Foliage/Public/FoliageType.h"
#include "FoliageEditorObject.generated.h"

class UFoliageType;

UCLASS()
class UFoliageEditorObject : public UObject
{
    GENERATED_UCLASS_BODY()

public:
    FORCEINLINE static UFoliageEditorObject* Get()
    {
        static UFoliageEditorObject* DefaultSettings = GetMutableDefault<UFoliageEditorObject>();
        if (DefaultSettings!= nullptr && !DefaultSettings->IsRooted())
        {
            DefaultSettings->AddToRoot();
        }
        return DefaultSettings;
    }

    FString GetAssetOfReplaceFoliage() const;

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replace Foliage", meta = (RelativeToGameContentDir, PathName))
    TWeakObjectPtr<UFoliageType> FoliageToReplace;
};
View Code

FoliageEditorObject.cpp

#include "FoliageEditorObject.h"


UFoliageEditorObject::UFoliageEditorObject(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    FoliageToReplace = NULL;
}

FString UFoliageEditorObject::GetAssetOfReplaceFoliage() const
{
    return FoliageToReplace->GetPathName();
}
View Code

在SFoliageEdit::Construct最后加上

FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
    FDetailsViewArgs DetailsViewArgs;
    DetailsViewArgs.bUpdatesFromSelection = true;
    DetailsViewArgs.bLockable = true;
    DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::ComponentsAndActorsUseNameArea;
    DetailsViewArgs.bCustomNameAreaLocation = false;
    DetailsViewArgs.bCustomFilterAreaLocation = true;
    DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Hide;
    SettingView = EditModule.CreateDetailView(DetailsViewArgs);

    ChildSlot
    [
        SNew(SVerticalBox)
        + SVerticalBox::Slot()
        .AutoHeight()
        .Padding(0, 0, 0, 5)
        [
            SAssignNew(ErrorText, SErrorText)
        ]
        + SVerticalBox::Slot()
        .Padding(0)
        [
            SNew(SVerticalBox)
            .IsEnabled(this, &SFoliageEdit::IsFoliageEditorEnabled)

        + SVerticalBox::Slot()
        .AutoHeight()
        [
            SNew(SHorizontalBox)
            .Visibility(this, &SFoliageEdit::GetVisibility_PaintDensity)

            + SHorizontalBox::Slot()
            .AutoWidth()
            .Padding(0.0f, 10.0f, 0.0f, 0.0f)
            [
                SNew(SHorizontalBox)
                + SHorizontalBox::Slot()
                .VAlign(VAlign_Center)
                [
                    SettingView->AsShared()
                ]
            ]
        ]
        // Foliage Palette
        + SVerticalBox::Slot()
        .FillHeight(1.f)
        .VAlign(VAlign_Fill)
        .Padding(0.f, 5.f, 0.f, 0.f)
        [
            SAssignNew(FoliagePalette, SFoliagePalette)
            .FoliageEdMode(FoliageEditMode)
        ]
        ]
    ];
    RefreshFullList();
View Code
void SFoliageEdit::RefreshFullList()
{
    FoliagePalette->UpdatePalette(true);
    FoliageEditorObject = UFoliageEditorObject::Get();
    SettingView->SetObject(FoliageEditorObject);
    FoliageEditMode->UpdateReplaceFoliageType(FoliageEditorObject->FoliageToReplace.Get());
}
View Code

写Slate很麻烦,一般也记不住,在需要的时候看下ue是怎么写的参考下就行。

 

 另外对美观有要求的 比如想区分下不同button的图片 见SlateEditorStyle.cpp 在对应的文件夹下加入新的图片就行

Set("FoliageEditMode.SelectInvalid",             new IMAGE_BRUSH("Icons/GeneralTools/SelectInvalid_40x", Icon20x20));
View Code

 

posted @ 2021-11-15 16:58  Adam_TT  阅读(250)  评论(0编辑  收藏  举报