Ejemplo n.º 1
0
void FComponentMaterialCategory::OnMaterialChanged( UMaterialInterface* NewMaterial, UMaterialInterface* PrevMaterial, int32 SlotIndex, bool bReplaceAll )
{
	// Whether or not we should begin a transaction on swap
	// Note we only begin a transaction on the first swap
	bool bShouldMakeTransaction = true;

	// Whether or not we made a transaction and need to end it
	bool bMadeTransaction = false;

	// Lambda to swap materials on a given component at the given slot index
	auto SwapMaterialLambda = []( UActorComponent* InComponent, int32 InElementIndex, UMaterialInterface* InNewMaterial )
	{
		UPrimitiveComponent* PrimitiveComp = Cast<UPrimitiveComponent>( InComponent );
		UDecalComponent* DecalComponent = Cast<UDecalComponent>( InComponent );

		if( PrimitiveComp )
		{
			PrimitiveComp->SetMaterial( InElementIndex, InNewMaterial );
		}
		else if( DecalComponent )
		{
			DecalComponent->SetMaterial( InElementIndex, InNewMaterial );
		}
	};

	// Scan the selected actors mesh components for the old material and swap it with the new material 
	for( FMaterialIterator It( SelectedComponents ); It; ++It )
	{
		int32 MaterialIndex = It.GetMaterialIndex();

		UActorComponent* CurrentComponent = It.GetComponent();

		if( CurrentComponent )
		{
			// Component materials can be replaced if they are not created from a blueprint (not exposed to the user) and have material overrides on the component
			bool bCanBeReplaced = 
				( CurrentComponent->IsA( UMeshComponent::StaticClass() ) ||
				CurrentComponent->IsA( UDecalComponent::StaticClass() ) ||
				CurrentComponent->IsA( UTextRenderComponent::StaticClass() ) ||
				CurrentComponent->IsA( ULandscapeComponent::StaticClass() ) );

			UMaterialInterface* Material = It.GetMaterial();
			// Check if the material is the same as the previous material or we are replaceing all in the same slot.  If so we will swap it with the new material
			if( bCanBeReplaced && ( Material == PrevMaterial || bReplaceAll ) && It.GetMaterialIndex() == SlotIndex )
			{
				// Begin a transaction for undo/redo the first time we encounter a material to replace.  
				// There is only one transaction for all replacement
				if( bShouldMakeTransaction && !bMadeTransaction )
				{
					GEditor->BeginTransaction( NSLOCTEXT("UnrealEd", "ReplaceComponentUsedMaterial", "Replace component used material") );

					bMadeTransaction = true;
				}

				UProperty* MaterialProperty = NULL;
				UObject* EditChangeObject = CurrentComponent;
				if( CurrentComponent->IsA( UMeshComponent::StaticClass() ) )
				{
					MaterialProperty = FindField<UProperty>( UMeshComponent::StaticClass(), "OverrideMaterials" );
				}
				else if( CurrentComponent->IsA( UDecalComponent::StaticClass() ) )
				{
					MaterialProperty = FindField<UProperty>( UDecalComponent::StaticClass(), "DecalMaterial" );
				}
				else if( CurrentComponent->IsA( UTextRenderComponent::StaticClass() ) )
				{
					MaterialProperty = FindField<UProperty>( UTextRenderComponent::StaticClass(), "TextMaterial" );
				}
				else if (CurrentComponent->IsA<ULandscapeComponent>() )
				{
					MaterialProperty = FindField<UProperty>( ALandscapeProxy::StaticClass(), "LandscapeMaterial" );
					EditChangeObject = CastChecked<ULandscapeComponent>(CurrentComponent)->GetLandscapeProxy();
				}

				// Add a navigation update lock only if the component world is valid
				TSharedPtr<FNavigationLockContext> NavUpdateLock;
				UWorld* World = CurrentComponent->GetWorld();
				if( World )
				{
					NavUpdateLock = MakeShareable( new FNavigationLockContext(World, ENavigationLockReason::MaterialUpdate) );
				}

				EditChangeObject->PreEditChange( MaterialProperty );

				if( NotifyHook && MaterialProperty )
				{
					NotifyHook->NotifyPreChange( MaterialProperty );
				}

				SwapMaterialLambda( CurrentComponent, It.GetMaterialIndex(), NewMaterial );

				FPropertyChangedEvent PropertyChangedEvent( MaterialProperty );
				EditChangeObject->PostEditChangeProperty( PropertyChangedEvent );

				if( NotifyHook && MaterialProperty )
				{
					NotifyHook->NotifyPostChange( PropertyChangedEvent, MaterialProperty );
				}

				// Propagate material change to instances of the edited component template
				if( !FApp::IsGame() )
				{
					TArray<UObject*> ComponentArchetypeInstances;
					if( CurrentComponent->HasAnyFlags(RF_ArchetypeObject) )
					{
						CurrentComponent->GetArchetypeInstances(ComponentArchetypeInstances);
					}
					else if( UObject* Outer = CurrentComponent->GetOuter() )
					{
						TArray<UObject*> OuterArchetypeInstances;
						Outer->GetArchetypeInstances(OuterArchetypeInstances);
						for( auto OuterArchetypeInstance : OuterArchetypeInstances )
						{
							if( UObject* ArchetypeInstance = static_cast<UObject*>(FindObjectWithOuter(OuterArchetypeInstance, CurrentComponent->GetClass(), CurrentComponent->GetFName())) )
							{
								ComponentArchetypeInstances.Add(ArchetypeInstance);
							}
						}
					}

					for( auto ComponentArchetypeInstance : ComponentArchetypeInstances )
					{
						CurrentComponent = CastChecked<UActorComponent>( ComponentArchetypeInstance );
						if( CurrentComponent->IsA<ULandscapeComponent>() )
						{
							ComponentArchetypeInstance = CastChecked<ULandscapeComponent>(CurrentComponent)->GetLandscapeProxy();
						}

						// Reset the navigation update lock if necessary
						UWorld* PreviousWorld = World;
						World = CurrentComponent->GetWorld();
						if( PreviousWorld != World )
						{
							NavUpdateLock = MakeShareable( new FNavigationLockContext(World, ENavigationLockReason::MaterialUpdate) );
						}

						ComponentArchetypeInstance->PreEditChange( MaterialProperty );

						SwapMaterialLambda( CurrentComponent, It.GetMaterialIndex(), NewMaterial );

						ComponentArchetypeInstance->PostEditChangeProperty( PropertyChangedEvent );
					}
				}
			}
		}
	}

	if( bMadeTransaction )
	{
		// End the transation if we created one
		GEditor->EndTransaction();
		// Redraw viewports to reflect the material changes 
		GUnrealEd->RedrawLevelEditingViewports();
	}
}