// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshVertexPaintingTool.h" #include "InteractiveToolManager.h" #include "Components/MeshComponent.h" #include "IMeshPaintComponentAdapter.h" #include "ComponentReregisterContext.h" #include "ToolDataVisualizer.h" #include "MeshPaintHelpers.h" #include "BaseGizmos/BrushStampIndicator.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MeshVertexPaintingTool) #define LOCTEXT_NAMESPACE "MeshVertexBrush" /* * ToolBuilder */ bool UMeshVertexColorPaintingToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return GEngine->GetEngineSubsystem()->GetSelectionSupportsVertexPaint(); } UInteractiveTool* UMeshVertexColorPaintingToolBuilder::BuildTool(const FToolBuilderState& SceneState) const { UMeshVertexColorPaintingTool* NewTool = NewObject(SceneState.ToolManager); return NewTool; } bool UMeshVertexWeightPaintingToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return GEngine->GetEngineSubsystem()->GetSelectionSupportsVertexPaint(); } UInteractiveTool* UMeshVertexWeightPaintingToolBuilder::BuildTool(const FToolBuilderState& SceneState) const { UMeshVertexWeightPaintingTool* NewTool = NewObject(SceneState.ToolManager); return NewTool; } /* * Tool */ UMeshVertexPaintingToolProperties::UMeshVertexPaintingToolProperties() :UMeshPaintingToolProperties(), VertexPreviewSize(6.0f) { } UMeshVertexPaintingTool::UMeshVertexPaintingTool() { PropertyClass = UMeshVertexPaintingToolProperties::StaticClass(); } bool UMeshVertexPaintingTool::IsMeshAdapterSupported(TSharedPtr MeshAdapter) const { return MeshAdapter.IsValid() ? MeshAdapter->SupportsVertexPaint() : false; } void UMeshVertexPaintingTool::Setup() { Super::Setup(); bResultValid = false; bStampPending = false; FMeshPaintToolSettingHelpers::RestorePropertiesForClassHeirachy(this, BrushProperties); VertexProperties = Cast(BrushProperties); // Needed after restoring properties because the brush radius may be an output // property based on selection, so we shouldn't use the last stored value there. // We wouldn't have this problem if we restore properties before getting // BrushRelativeSizeRange, but that happens in the Super::Setup() call earlier. RecalculateBrushRadius(); BrushStampIndicator->LineColor = FLinearColor::Green; SelectionMechanic = NewObject(this); SelectionMechanic->Setup(this); } void UMeshVertexPaintingTool::Shutdown(EToolShutdownType ShutdownType) { //If we're painting vertex colors then propagate the painting done on LOD0 to all lower LODs. //Then stop forcing the LOD level of the mesh to LOD0. ApplyForcedLODIndex(-1); FinishPainting(); UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem(); if (MeshPaintingSubsystem) { MeshPaintingSubsystem->Refresh(); } FMeshPaintToolSettingHelpers::SavePropertiesForClassHeirachy(this, BrushProperties); Super::Shutdown(ShutdownType); } void UMeshVertexPaintingTool::Render(IToolsContextRenderAPI* RenderAPI) { Super::Render(RenderAPI); FToolDataVisualizer Draw; Draw.BeginFrame(RenderAPI); UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem(); if (MeshPaintingSubsystem && LastBestHitResult.Component != nullptr && MeshPaintingSubsystem->GetPaintableMeshComponents().Num() > 0) { BrushStampIndicator->bDrawIndicatorLines = true; static float WidgetLineThickness = 1.0f; static FLinearColor VertexPointColor = FLinearColor::White; static FLinearColor HoverVertexPointColor = FLinearColor(0.3f, 1.0f, 0.3f); const float NormalLineSize(BrushProperties->BrushRadius * 0.35f); // Make the normal line length a function of brush size static const FLinearColor NormalLineColor(0.3f, 1.0f, 0.3f); const FLinearColor BrushCueColor = (bArePainting ? FLinearColor(1.0f, 1.0f, 0.3f) : FLinearColor(0.3f, 1.0f, 0.3f)); const FLinearColor InnerBrushCueColor = (bArePainting ? FLinearColor(0.5f, 0.5f, 0.1f) : FLinearColor(0.1f, 0.5f, 0.1f)); const float PointDrawSize = VertexProperties->VertexPreviewSize; // Draw trace surface normal const FVector NormalLineEnd(LastBestHitResult.Location + LastBestHitResult.Normal * NormalLineSize); Draw.DrawLine(FVector(LastBestHitResult.Location), NormalLineEnd, NormalLineColor, WidgetLineThickness); for (UMeshComponent* CurrentComponent : MeshPaintingSubsystem->GetPaintableMeshComponents()) { TSharedPtr MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(Cast(CurrentComponent)); if (MeshAdapter && MeshAdapter->SupportsVertexPaint()) { const FMatrix ComponentToWorldMatrix = MeshAdapter->GetComponentToWorldMatrix(); FViewCameraState CameraState; GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(CameraState.Position)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(LastBestHitResult.Location)); // @todo MeshPaint: Input vector doesn't work well with non-uniform scale const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushProperties->BrushRadius, 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; const TArray& InRangeVertices = MeshAdapter->SphereIntersectVertices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, VertexProperties->bOnlyFrontFacingTriangles); for (const FVector& Vertex : InRangeVertices) { const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex); if ((LastBestHitResult.Location - WorldPositionVertex).Size() <= BrushProperties->BrushRadius) { const float VisualBiasDistance = 0.15f; const FVector VertexVisualPosition = WorldPositionVertex + LastBestHitResult.Normal * VisualBiasDistance; Draw.DrawPoint(VertexVisualPosition, HoverVertexPointColor, PointDrawSize, SDPG_World); } } } } } else { BrushStampIndicator->bDrawIndicatorLines = false; } Draw.EndFrame(); UpdateResult(); } void UMeshVertexPaintingTool::OnTick(float DeltaTime) { if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { if (MeshPaintingSubsystem->bNeedsRecache) { CacheSelectionData(); } } if (bStampPending) { Paint(PendingStampRay.Origin, PendingStampRay.Direction); bStampPending = false; // flow if (bInDrag && VertexProperties && VertexProperties->bEnableFlow) { bStampPending = true; } } } void UMeshVertexPaintingTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) { Super::OnPropertyModified(PropertySet, Property); bResultValid = false; } double UMeshVertexPaintingTool::EstimateMaximumTargetDimension() { if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { FBoxSphereBounds::Builder ExtentsBuilder; for (UMeshComponent* SelectedComponent : MeshPaintingSubsystem->GetSelectedMeshComponents()) { ExtentsBuilder += SelectedComponent->Bounds; } if (ExtentsBuilder.IsValid()) { return FBoxSphereBounds(ExtentsBuilder).BoxExtent.GetAbsMax(); } } return Super::EstimateMaximumTargetDimension(); } double UMeshVertexPaintingTool::CalculateTargetEdgeLength(int TargetTriCount) { double TargetTriArea = InitialMeshArea / (double)TargetTriCount; double EdgeLen = (TargetTriArea); return (double)FMath::RoundToInt(EdgeLen*100.0) / 100.0; } bool UMeshVertexPaintingTool::Paint(const FVector& InRayOrigin, const FVector& InRayDirection) { // Determine paint action according to whether or not shift is held down const EMeshPaintModeAction PaintAction = GetShiftToggle() ? EMeshPaintModeAction::Erase : EMeshPaintModeAction::Paint; const float PaintStrength = 1.0f; //Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f; // Handle internal painting functionality TPair Ray(InRayOrigin, InRayDirection); return PaintInternal(MakeArrayView(&Ray, 1), PaintAction, PaintStrength); } bool UMeshVertexPaintingTool::Paint(const TArrayView>& Rays) { // Determine paint action according to whether or not shift is held down const EMeshPaintModeAction PaintAction = GetShiftToggle() ? EMeshPaintModeAction::Erase : EMeshPaintModeAction::Paint; const float PaintStrength = 1.0f; //Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f; // Handle internal painting functionality return PaintInternal(Rays, PaintAction, PaintStrength); } bool UMeshVertexPaintingTool::PaintInternal(const TArrayView>& Rays, EMeshPaintModeAction PaintAction, float PaintStrength) { TArray PaintRayResults; PaintRayResults.AddDefaulted(Rays.Num()); LastBestHitResult.Reset(); TMap> HoveredComponents; UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem(); const float BrushRadius = BrushProperties->BrushRadius; const bool bIsPainting = PaintAction == EMeshPaintModeAction::Paint; const float InStrengthScale = PaintStrength; bool bPaintApplied = false; // Fire out a ray to see if there is a *selected* component under the mouse cursor that can be painted. for (int32 i = 0; i < Rays.Num(); ++i) { const FVector& RayOrigin = Rays[i].Key; const FVector& RayDirection = Rays[i].Value; FRay Ray = FRay(RayOrigin, RayDirection); FHitResult& BestTraceResult = PaintRayResults[i].BestTraceResult; const FVector TraceStart(RayOrigin); const FVector TraceEnd(RayOrigin + RayDirection * HALF_WORLD_MAX); for (UMeshComponent* MeshComponent : MeshPaintingSubsystem->GetPaintableMeshComponents()) { TSharedPtr MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(MeshComponent); // Ray trace FHitResult TraceHitResult(1.0f); if (MeshAdapter->LineTraceComponent(TraceHitResult, TraceStart, TraceEnd, FCollisionQueryParams(SCENE_QUERY_STAT(Paint), true))) { // Find the closest impact if ((BestTraceResult.GetComponent() == nullptr) || (TraceHitResult.Time < BestTraceResult.Time)) { BestTraceResult = TraceHitResult; } } } bool bUsed = false; if (BestTraceResult.GetComponent() != nullptr) { FBox BrushBounds = FBox::BuildAABB(BestTraceResult.Location, FVector(BrushRadius * 1.25f, BrushRadius * 1.25f, BrushRadius * 1.25f)); // Vertex paint mode, so we want all valid components overlapping the brush hit location for (auto TestComponent : MeshPaintingSubsystem->GetPaintableMeshComponents()) { const FBox ComponentBounds = TestComponent->Bounds.GetBox(); TSharedPtr MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(TestComponent); if (MeshAdapter && ComponentBounds.Intersect(BrushBounds)) { // OK, this mesh potentially overlaps the brush! HoveredComponents.FindOrAdd(TestComponent).Add(i); bUsed = true; } } } if (bUsed) { FVector BrushXAxis, BrushYAxis; BestTraceResult.Normal.FindBestAxisVectors(BrushXAxis, BrushYAxis); // Display settings const float VisualBiasDistance = 0.15f; const FVector BrushVisualPosition = BestTraceResult.Location + BestTraceResult.Normal * VisualBiasDistance; const FLinearColor PaintColor = VertexProperties->PaintColor; const FLinearColor EraseColor = VertexProperties->EraseColor; // NOTE: We square the brush strength to maximize slider precision in the low range const float BrushStrength = BrushProperties->BrushStrength * BrushProperties->BrushStrength * InStrengthScale; const float BrushDepth = BrushRadius; LastBestHitResult = BestTraceResult; // Mesh paint settings FMeshPaintParameters& Params = PaintRayResults[i].Params; Params.PaintAction = PaintAction; Params.BrushPosition = BestTraceResult.Location; Params.BrushNormal = BestTraceResult.Normal; Params.BrushColor = bIsPainting ? PaintColor : EraseColor; Params.SquaredBrushRadius = BrushRadius * BrushRadius; Params.BrushRadialFalloffRange = BrushProperties->BrushFalloffAmount * BrushRadius; Params.InnerBrushRadius = BrushRadius - Params.BrushRadialFalloffRange; Params.BrushDepth = BrushDepth; Params.BrushDepthFalloffRange = BrushProperties->BrushFalloffAmount * BrushDepth; Params.BrushDepthFalloffRange = BrushProperties->BrushFalloffAmount * BrushDepth; Params.InnerBrushDepth = BrushDepth - Params.BrushDepthFalloffRange; Params.BrushStrength = BrushStrength; Params.BrushToWorldMatrix = FMatrix(BrushXAxis, BrushYAxis, Params.BrushNormal, Params.BrushPosition); Params.InverseBrushToWorldMatrix = Params.BrushToWorldMatrix.Inverse(); SetAdditionalPaintParameters(Params); } } if (HoveredComponents.Num() > 0) { if (bArePainting == false) { // Vertex painting is an ongoing transaction, while texture painting is handled separately later in a single transaction GetToolManager()->BeginUndoTransaction(LOCTEXT("MeshPaintMode_VertexPaint_TransactionPaintStroke", "Vertex Paint")); bArePainting = true; } // Iterate over the selected meshes under the cursor and paint them! for (auto& Entry : HoveredComponents) { UMeshComponent* HoveredComponent = Entry.Key; TArray& PaintRayResultIds = Entry.Value; IMeshPaintComponentAdapter* MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(HoveredComponent).Get(); if (!ensure(MeshAdapter)) { continue; } if (MeshAdapter->SupportsVertexPaint()) { FPerVertexPaintActionArgs Args; Args.Adapter = MeshAdapter; FViewCameraState CameraState; GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); Args.CameraPosition = CameraState.Position; Args.BrushProperties = VertexProperties; Args.Action = PaintAction; bool bMeshPreEditCalled = false; TSet InfluencedVertices; for (int32 PaintRayResultId : PaintRayResultIds) { InfluencedVertices.Reset(); Args.HitResult = PaintRayResults[PaintRayResultId].BestTraceResult; bPaintApplied |= MeshPaintingSubsystem->GetPerVertexPaintInfluencedVertices(Args, InfluencedVertices); if (InfluencedVertices.Num() == 0) { continue; } if (!bMeshPreEditCalled) { bMeshPreEditCalled = true; MeshAdapter->PreEdit(); } for (const int32 VertexIndex : InfluencedVertices) { ApplyVertexData(Args, VertexIndex, PaintRayResults[PaintRayResultId].Params); } } if (bMeshPreEditCalled) { MeshAdapter->PostEdit(); } } } } return bPaintApplied; } void UMeshVertexPaintingTool::ApplyVertexData(FPerVertexPaintActionArgs& InArgs, int32 VertexIndex, FMeshPaintParameters Parameters) { /** Retrieve vertex position and color for applying vertex painting */ FColor PaintColor; FVector Position; InArgs.Adapter->GetVertexPosition(VertexIndex, Position); Position = InArgs.Adapter->GetComponentToWorldMatrix().TransformPosition(Position); InArgs.Adapter->GetVertexColor(VertexIndex, PaintColor, true); GEngine->GetEngineSubsystem()->PaintVertex(Position, Parameters, PaintColor); InArgs.Adapter->SetVertexColor(VertexIndex, PaintColor, true); } void UMeshVertexPaintingTool::UpdateResult() { GetToolManager()->PostInvalidation(); bResultValid = true; } FInputRayHit UMeshVertexPaintingTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos) { FHitResult OutHit; bCachedClickRay = false; if (!HitTest(PressPos.WorldRay, OutHit)) { UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem(); const bool bFallbackClick = MeshPaintingSubsystem->GetSelectedMeshComponents().Num() > 0; if (SelectionMechanic->IsHitByClick(PressPos, bFallbackClick).bHit) { bCachedClickRay = true; PendingClickRay = PressPos.WorldRay; PendingClickScreenPosition = PressPos.ScreenPosition; return FInputRayHit(0.0); } } return Super::CanBeginClickDragSequence(PressPos); } void UMeshVertexPaintingTool::OnUpdateModifierState(int ModifierID, bool bIsOn) { Super::OnUpdateModifierState(ModifierID, bIsOn); SelectionMechanic->SetAddToSelectionSet(bShiftToggle); } void UMeshVertexPaintingTool::OnBeginDrag(const FRay& Ray) { Super::OnBeginDrag(Ray); FHitResult OutHit; if (HitTest(Ray, OutHit)) { bInDrag = true; // apply initial stamp PendingStampRay = Ray; bStampPending = true; } else if (bCachedClickRay) { FInputDeviceRay InputDeviceRay = FInputDeviceRay(PendingClickRay, PendingClickScreenPosition); SelectionMechanic->SetAddToSelectionSet(bShiftToggle); SelectionMechanic->OnClicked(InputDeviceRay); bCachedClickRay = false; RecalculateBrushRadius(); } } void UMeshVertexPaintingTool::OnUpdateDrag(const FRay& Ray) { Super::OnUpdateDrag(Ray); if (bInDrag) { PendingStampRay = Ray; bStampPending = true; } } void UMeshVertexPaintingTool::OnEndDrag(const FRay& Ray) { FinishPainting(); bStampPending = false; bInDrag = false; } bool UMeshVertexPaintingTool::HitTest(const FRay& Ray, FHitResult& OutHit) { bool bUsed = false; UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem(); if (MeshPaintingSubsystem) { MeshPaintingSubsystem->FindHitResult(Ray, OutHit); LastBestHitResult = OutHit; bUsed = OutHit.bBlockingHit; } return bUsed; } void UMeshVertexPaintingTool::FinishPainting() { if (bArePainting) { bArePainting = false; GetToolManager()->EndUndoTransaction(); OnPaintingFinishedDelegate.ExecuteIfBound(); } } UMeshVertexColorPaintingTool::UMeshVertexColorPaintingTool() { PropertyClass = UMeshVertexColorPaintingToolProperties::StaticClass(); } void UMeshVertexColorPaintingTool::Setup() { Super::Setup(); ColorProperties = Cast(BrushProperties); GetToolManager()->DisplayMessage( LOCTEXT("OnStartColorPaintTool", "Paint vertex colors on selected meshes. Use the Color View Mode to preview your applied changes."), EToolMessageLevel::UserNotification); } void UMeshVertexPaintingTool::CacheSelectionData() { if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { MeshPaintingSubsystem->ClearPaintableMeshComponents(); // Update(cached) Paint LOD level if necessary VertexProperties->LODIndex = FMath::Min(VertexProperties->LODIndex, GetMaxLODIndexToPaint()); CachedLODIndex = VertexProperties->LODIndex; bCachedForceLOD = VertexProperties->bPaintOnSpecificLOD; //Determine LOD level to use for painting(can only paint on LODs in vertex mode) const int32 PaintLODIndex = VertexProperties->bPaintOnSpecificLOD ? VertexProperties->LODIndex : 0; //Determine UV channel to use while painting textures const int32 UVChannel = 0; MeshPaintingSubsystem->CacheSelectionData(PaintLODIndex, UVChannel); } } void UMeshVertexColorPaintingTool::SetAdditionalPaintParameters(FMeshPaintParameters& InPaintParameters) { InPaintParameters.bWriteRed = ColorProperties->bWriteRed; InPaintParameters.bWriteGreen = ColorProperties->bWriteGreen; InPaintParameters.bWriteBlue = ColorProperties->bWriteBlue; InPaintParameters.bWriteAlpha = ColorProperties->bWriteAlpha; InPaintParameters.ApplyVertexDataDelegate.AddUObject(GEngine->GetEngineSubsystem(), &UMeshPaintingSubsystem::ApplyVertexColorPaint); } int32 UMeshVertexPaintingTool::GetMaxLODIndexToPaint() const { //The maximum LOD we can paint is decide by the lowest number of LOD in the selection int32 LODMin = TNumericLimits::Max(); if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { TArray SelectedComponents = MeshPaintingSubsystem->GetSelectedMeshComponents(); for (UMeshComponent* MeshComponent : SelectedComponents) { int32 NumMeshLODs = 0; if (MeshPaintingSubsystem->TryGetNumberOfLODs(MeshComponent, NumMeshLODs)) { ensure(NumMeshLODs > 0); LODMin = FMath::Min(LODMin, NumMeshLODs - 1); } } if (LODMin == TNumericLimits::Max()) { LODMin = 1; } } return LODMin; } void UMeshVertexPaintingTool::LODPaintStateChanged(const bool bLODPaintingEnabled) { bool AbortChange = false; // Set actual flag in the settings struct VertexProperties->bPaintOnSpecificLOD = bLODPaintingEnabled; if (!bLODPaintingEnabled) { // Reset painting LOD index VertexProperties->LODIndex = 0; } ApplyForcedLODIndex(bLODPaintingEnabled ? CachedLODIndex : -1); if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { TArray PaintableComponents = MeshPaintingSubsystem->GetPaintableMeshComponents(); TUniquePtr< FComponentReregisterContext > ComponentReregisterContext; //Make sure all static mesh render is dirty since we change the force LOD for (UMeshComponent* SelectedComponent : PaintableComponents) { if (SelectedComponent) { ComponentReregisterContext.Reset(new FComponentReregisterContext(SelectedComponent)); } } MeshPaintingSubsystem->Refresh(); } } void UMeshVertexPaintingTool::ApplyForcedLODIndex(int32 ForcedLODIndex) { if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { TArray PaintableComponents = MeshPaintingSubsystem->GetPaintableMeshComponents(); for (UMeshComponent* SelectedComponent : PaintableComponents) { if (SelectedComponent) { MeshPaintingSubsystem->ForceRenderMeshLOD(SelectedComponent, ForcedLODIndex); } } } } void UMeshVertexPaintingTool::PaintLODChanged() { // Enforced LOD for painting if (CachedLODIndex != VertexProperties->LODIndex) { CachedLODIndex = VertexProperties->LODIndex; ApplyForcedLODIndex(bCachedForceLOD ? CachedLODIndex : -1); TUniquePtr< FComponentReregisterContext > ComponentReregisterContext; //Make sure all static mesh render is dirty since we change the force LOD if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem()) { TArray PaintableComponents = MeshPaintingSubsystem->GetPaintableMeshComponents(); for (UMeshComponent* SelectedComponent : PaintableComponents) { if (SelectedComponent) { ComponentReregisterContext.Reset(new FComponentReregisterContext(SelectedComponent)); } } MeshPaintingSubsystem->Refresh(); } } } void UMeshVertexPaintingTool::CycleMeshLODs(int32 Direction) { if (bCachedForceLOD) { const int32 MaxLODIndex = GetMaxLODIndexToPaint() + 1; const int32 NewLODIndex = VertexProperties->LODIndex + Direction; const int32 AdjustedLODIndex = NewLODIndex < 0 ? MaxLODIndex + NewLODIndex : NewLODIndex % MaxLODIndex; VertexProperties->LODIndex = AdjustedLODIndex; PaintLODChanged(); } } UMeshVertexWeightPaintingToolProperties::UMeshVertexWeightPaintingToolProperties() :UMeshVertexPaintingToolProperties(), TextureWeightType(EMeshPaintWeightTypes::AlphaLerp), PaintTextureWeightIndex(EMeshPaintTextureIndex::TextureOne), EraseTextureWeightIndex(EMeshPaintTextureIndex::TextureTwo) { } UMeshVertexWeightPaintingTool::UMeshVertexWeightPaintingTool() { PropertyClass = UMeshVertexWeightPaintingToolProperties::StaticClass(); } void UMeshVertexWeightPaintingTool::Setup() { Super::Setup(); WeightProperties = Cast(BrushProperties); GetToolManager()->DisplayMessage( LOCTEXT("OnStartPaintWeightsTool", "Paint Vertex Weights on selected meshes."), EToolMessageLevel::UserNotification); } void UMeshVertexWeightPaintingTool::SetAdditionalPaintParameters(FMeshPaintParameters& InPaintParameters) { InPaintParameters.TotalWeightCount = (int32)WeightProperties->TextureWeightType; // Select texture weight index based on whether or not we're painting or erasing { const int32 PaintWeightIndex = InPaintParameters.PaintAction == EMeshPaintModeAction::Paint ? (int32)WeightProperties->PaintTextureWeightIndex : (int32)WeightProperties->EraseTextureWeightIndex; // Clamp the weight index to fall within the total weight count InPaintParameters.PaintWeightIndex = FMath::Clamp(PaintWeightIndex, 0, InPaintParameters.TotalWeightCount - 1); } InPaintParameters.ApplyVertexDataDelegate.AddUObject(GEngine->GetEngineSubsystem(), &UMeshPaintingSubsystem::ApplyVertexWeightPaint); } #undef LOCTEXT_NAMESPACE