void SMaterialEditorViewport::OnSetPreviewMeshFromSelection() { bool bFoundPreviewMesh = false; FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); UMaterialInterface* MaterialInterface = MaterialEditorPtr.Pin()->GetMaterialInterface(); // Look for a selected asset that can be converted to a mesh component for (FSelectionIterator SelectionIt(*GEditor->GetSelectedObjects()); SelectionIt && !bFoundPreviewMesh; ++SelectionIt) { UObject* TestAsset = *SelectionIt; if (TestAsset->IsAsset()) { if (TSubclassOf<UActorComponent> ComponentClass = FComponentAssetBrokerage::GetPrimaryComponentForAsset(TestAsset->GetClass())) { if (ComponentClass->IsChildOf(UMeshComponent::StaticClass())) { if (USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(TestAsset)) { // Special case handling for skeletal meshes, sets the material to be usable with them if (MaterialInterface->GetMaterial()) { bool bNeedsRecompile = false; MaterialInterface->GetMaterial()->SetMaterialUsage(bNeedsRecompile, MATUSAGE_SkeletalMesh); } } SetPreviewAsset(TestAsset); MaterialInterface->PreviewMesh = TestAsset->GetPathName(); bFoundPreviewMesh = true; } } } } if (bFoundPreviewMesh) { FMaterialEditor::UpdateThumbnailInfoPreviewMesh(MaterialInterface); MaterialInterface->MarkPackageDirty(); RefreshViewport(); } else { FSuppressableWarningDialog::FSetupInfo Info(NSLOCTEXT("UnrealEd", "Warning_NoPreviewMeshFound_Message", "You need to select a mesh-based asset in the content browser to preview it."), NSLOCTEXT("UnrealEd", "Warning_NoPreviewMeshFound", "Warning: No Preview Mesh Found"), "Warning_NoPreviewMeshFound"); Info.ConfirmText = NSLOCTEXT("UnrealEd", "Warning_NoPreviewMeshFound_Confirm", "Continue"); FSuppressableWarningDialog NoPreviewMeshWarning( Info ); NoPreviewMeshWarning.ShowModal(); } }
void FStaticMeshEditorViewportClient::ProcessClick(class FSceneView& InView, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY) { const bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); bool ClearSelectedSockets = true; bool ClearSelectedPrims = true; bool ClearSelectedEdges = true; if( HitProxy ) { if(HitProxy->IsA( HSMESocketProxy::StaticGetType() ) ) { HSMESocketProxy* SocketProxy = (HSMESocketProxy*)HitProxy; UStaticMeshSocket* Socket = NULL; if(SocketProxy->SocketIndex < StaticMesh->Sockets.Num()) { Socket = StaticMesh->Sockets[SocketProxy->SocketIndex]; } if(Socket) { StaticMeshEditorPtr.Pin()->SetSelectedSocket(Socket); } ClearSelectedSockets = false; } else if (HitProxy->IsA(HSMECollisionProxy::StaticGetType()) && StaticMesh->BodySetup) { HSMECollisionProxy* CollisionProxy = (HSMECollisionProxy*)HitProxy; if (StaticMeshEditorPtr.Pin()->IsSelectedPrim(CollisionProxy->PrimData)) { if (!bCtrlDown) { StaticMeshEditorPtr.Pin()->AddSelectedPrim(CollisionProxy->PrimData, true); } else { StaticMeshEditorPtr.Pin()->RemoveSelectedPrim(CollisionProxy->PrimData); } } else { StaticMeshEditorPtr.Pin()->AddSelectedPrim(CollisionProxy->PrimData, !bCtrlDown); } // Force the widget to translate, if not already set if (WidgetMode == FWidget::WM_None) { WidgetMode = FWidget::WM_Translate; } ClearSelectedPrims = false; } } else { const bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift); if(!bCtrlDown && !bShiftDown) { SelectedEdgeIndices.Empty(); } // Check to see if we clicked on a mesh edge if( StaticMeshComponent != NULL && Viewport->GetSizeXY().X > 0 && Viewport->GetSizeXY().Y > 0 ) { FSceneViewFamilyContext ViewFamily( FSceneViewFamily::ConstructionValues( Viewport, GetScene(), EngineShowFlags )); FSceneView* View = CalcSceneView(&ViewFamily); FViewportClick ViewportClick(View, this, Key, Event, HitX, HitY); const FVector ClickLineStart( ViewportClick.GetOrigin() ); const FVector ClickLineEnd( ViewportClick.GetOrigin() + ViewportClick.GetDirection() * HALF_WORLD_MAX ); // Don't bother doing a line check as there is only one mesh in the SME and it makes fuzzy selection difficult // FHitResult CheckResult( 1.0f ); // if( StaticMeshComponent->LineCheck( // CheckResult, // In/Out: Result // ClickLineEnd, // Target // ClickLineStart, // Source // FVector::ZeroVector, // Extend // TRACE_ComplexCollision ) ) // Trace flags { // @todo: Should be in screen space ideally const float WorldSpaceMinClickDistance = 100.0f; float ClosestEdgeDistance = FLT_MAX; TArray< int32 > ClosestEdgeIndices; FVector ClosestEdgeVertices[ 2 ]; const uint32 LODLevel = FMath::Clamp( StaticMeshComponent->ForcedLodModel - 1, 0, StaticMeshComponent->StaticMesh->GetNumLODs() - 1 ); FRawMesh RawMesh; StaticMeshComponent->StaticMesh->SourceModels[LODLevel].RawMeshBulkData->LoadRawMesh(RawMesh); const int32 RawEdgeCount = RawMesh.WedgeIndices.Num() - 1; const int32 NumFaces = RawMesh.WedgeIndices.Num() / 3; int32 NumBackFacingTriangles = 0; for(int32 FaceIndex = 0; FaceIndex < NumFaces; ++FaceIndex) { // We disable edge selection where all adjoining triangles are back face culled and the // material is not two-sided. This prevents edges that are back-face culled from being selected. bool bIsBackFacing = false; bool bIsTwoSided = false; UMaterialInterface* Material = StaticMeshComponent->GetMaterial(RawMesh.FaceMaterialIndices[FaceIndex]); if (Material && Material->GetMaterial()) { bIsTwoSided = Material->IsTwoSided(); } if(!bIsTwoSided) { // Check whether triangle if back facing const FVector A = RawMesh.GetWedgePosition( FaceIndex * 3); const FVector B = RawMesh.GetWedgePosition( FaceIndex * 3 + 1); const FVector C = RawMesh.GetWedgePosition( FaceIndex * 3 + 2); // Compute the per-triangle normal const FVector BA = A - B; const FVector CA = A - C; const FVector TriangleNormal = (CA ^ BA).SafeNormal(); // Transform the view position from world to component space const FVector ComponentSpaceViewOrigin = StaticMeshComponent->ComponentToWorld.InverseTransformPosition( View->ViewMatrices.ViewOrigin); // Determine which side of the triangle's plane that the view position lies on. bIsBackFacing = (FVector::PointPlaneDist( ComponentSpaceViewOrigin, A, TriangleNormal) < 0.0f); } for( int32 VertIndex = 0; VertIndex < 3; ++VertIndex ) { const int32 EdgeIndex = FaceIndex * 3 + VertIndex; const int32 EdgeIndex2 = FaceIndex * 3 + ((VertIndex + 1) % 3); FVector EdgeVertices[ 2 ]; EdgeVertices[0] = RawMesh.GetWedgePosition(EdgeIndex); EdgeVertices[1] = RawMesh.GetWedgePosition(EdgeIndex2); // First check to see if this edge is already in our "closest to click" list. // Most edges are shared by two faces in our raw triangle data set, so we want // to select (or deselect) both of these edges that the user clicks on (what // appears to be) a single edge if( ClosestEdgeIndices.Num() > 0 && ( ( EdgeVertices[ 0 ].Equals( ClosestEdgeVertices[ 0 ] ) && EdgeVertices[ 1 ].Equals( ClosestEdgeVertices[ 1 ] ) ) || ( EdgeVertices[ 0 ].Equals( ClosestEdgeVertices[ 1 ] ) && EdgeVertices[ 1 ].Equals( ClosestEdgeVertices[ 0 ] ) ) ) ) { // Edge overlaps the closest edge we have so far, so just add it to the list ClosestEdgeIndices.Add( EdgeIndex ); // Increment the number of back facing triangles if the adjoining triangle // is back facing and isn't two-sided if(bIsBackFacing && !bIsTwoSided) { ++NumBackFacingTriangles; } } else { FVector WorldSpaceEdgeStart( StaticMeshComponent->ComponentToWorld.TransformPosition( EdgeVertices[ 0 ] ) ); FVector WorldSpaceEdgeEnd( StaticMeshComponent->ComponentToWorld.TransformPosition( EdgeVertices[ 1 ] ) ); // Determine the mesh edge that's closest to the ray cast through the eye towards the click location FVector ClosestPointToEdgeOnClickLine; FVector ClosestPointToClickLineOnEdge; FMath::SegmentDistToSegment( ClickLineStart, ClickLineEnd, WorldSpaceEdgeStart, WorldSpaceEdgeEnd, ClosestPointToEdgeOnClickLine, ClosestPointToClickLineOnEdge ); // Compute the minimum distance (squared) const float MinDistanceToEdgeSquared = ( ClosestPointToClickLineOnEdge - ClosestPointToEdgeOnClickLine ).SizeSquared(); if( MinDistanceToEdgeSquared <= WorldSpaceMinClickDistance ) { if( MinDistanceToEdgeSquared <= ClosestEdgeDistance ) { // This is the closest edge to the click line that we've found so far! ClosestEdgeDistance = MinDistanceToEdgeSquared; ClosestEdgeVertices[ 0 ] = EdgeVertices[ 0 ]; ClosestEdgeVertices[ 1 ] = EdgeVertices[ 1 ]; ClosestEdgeIndices.Reset(); ClosestEdgeIndices.Add( EdgeIndex ); // Reset the number of back facing triangles. NumBackFacingTriangles = (bIsBackFacing && !bIsTwoSided) ? 1 : 0; } } } } } // Did the user click on an edge? Edges must also have at least one adjoining triangle // which isn't back face culled (for one-sided materials) if( ClosestEdgeIndices.Num() > 0 && ClosestEdgeIndices.Num() > NumBackFacingTriangles) { for( int32 CurIndex = 0; CurIndex < ClosestEdgeIndices.Num(); ++CurIndex ) { const int32 CurEdgeIndex = ClosestEdgeIndices[ CurIndex ]; if( bCtrlDown ) { // Toggle selection if( SelectedEdgeIndices.Contains( CurEdgeIndex ) ) { SelectedEdgeIndices.Remove( CurEdgeIndex ); } else { SelectedEdgeIndices.Add( CurEdgeIndex ); } } else { // Append to selection SelectedEdgeIndices.Add( CurEdgeIndex ); } } // Reset cached vertices and uv coordinates. SelectedEdgeVertices.Reset(); for(int32 TexCoordIndex = 0; TexCoordIndex < MAX_STATIC_TEXCOORDS; ++TexCoordIndex) { SelectedEdgeTexCoords[TexCoordIndex].Reset(); } for(FSelectedEdgeSet::TIterator SelectionIt( SelectedEdgeIndices ); SelectionIt; ++SelectionIt) { const uint32 EdgeIndex = *SelectionIt; const uint32 FaceIndex = EdgeIndex / 3; const uint32 WedgeIndex = FaceIndex * 3 + (EdgeIndex % 3); const uint32 WedgeIndex2 = FaceIndex * 3 + ((EdgeIndex + 1) % 3); // Cache edge vertices in local space. FVector EdgeVertices[ 2 ]; EdgeVertices[ 0 ] = RawMesh.GetWedgePosition(WedgeIndex); EdgeVertices[ 1 ] = RawMesh.GetWedgePosition(WedgeIndex2); SelectedEdgeVertices.Add(EdgeVertices[0]); SelectedEdgeVertices.Add(EdgeVertices[1]); // Cache UV for(int32 TexCoordIndex = 0; TexCoordIndex < MAX_STATIC_TEXCOORDS; ++TexCoordIndex) { if( RawMesh.WedgeTexCoords[TexCoordIndex].Num() > 0) { FVector2D UVIndex1, UVIndex2; UVIndex1 = RawMesh.WedgeTexCoords[TexCoordIndex][WedgeIndex]; UVIndex2 = RawMesh.WedgeTexCoords[TexCoordIndex][WedgeIndex2]; SelectedEdgeTexCoords[TexCoordIndex].Add(UVIndex1); SelectedEdgeTexCoords[TexCoordIndex].Add(UVIndex2); } } } ClearSelectedEdges = false; } } } } if (ClearSelectedSockets && StaticMeshEditorPtr.Pin()->GetSelectedSocket()) { StaticMeshEditorPtr.Pin()->SetSelectedSocket(NULL); } if (ClearSelectedPrims) { StaticMeshEditorPtr.Pin()->ClearSelectedPrims(); } if (ClearSelectedEdges) { SelectedEdgeIndices.Empty(); } Invalidate(); }
void UTexture::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); SetLightingGuid(); // Determine whether any property that requires recompression of the texture, or notification to Materials has changed. bool RequiresNotifyMaterials = false; UProperty* PropertyThatChanged = PropertyChangedEvent.Property; if( PropertyThatChanged ) { FString PropertyName = *PropertyThatChanged->GetName(); if (FCString::Stricmp(*PropertyName, TEXT("CompressionSettings")) == 0) { RequiresNotifyMaterials = true; } bool bPreventSRGB = (CompressionSettings == TC_Alpha || CompressionSettings == TC_Normalmap || CompressionSettings == TC_Masks || CompressionSettings == TC_HDR); if(bPreventSRGB && SRGB == true) { SRGB = false; } } NumCinematicMipLevels = FMath::Max<int32>( NumCinematicMipLevels, 0 ); if( (PropertyChangedEvent.ChangeType & EPropertyChangeType::Interactive) == 0 ) { // Update the texture resource. This will recache derived data if necessary // which may involve recompressing the texture. UpdateResource(); } // Notify any loaded material instances if changed our compression format if (RequiresNotifyMaterials) { TArray<UMaterialInterface*> MaterialsThatUseThisTexture; // Create a material update context to safely update materials. { FMaterialUpdateContext UpdateContext; // Notify any material that uses this texture TSet<UMaterial*> BaseMaterialsThatUseThisTexture; for (TObjectIterator<UMaterialInterface> It; It; ++It) { UMaterialInterface* MaterialInterface = *It; if (DoesMaterialUseTexture(MaterialInterface,this)) { MaterialsThatUseThisTexture.Add(MaterialInterface); // This is a bit tricky. We want to make sure all materials using this texture are // updated. Materials are always updated. Material instances may also have to be // updated and if they have static permutations their children must be updated // whether they use the texture or not! The safe thing to do is to add the instance's // base material to the update context causing all materials in the tree to update. BaseMaterialsThatUseThisTexture.Add(MaterialInterface->GetMaterial()); } } // Go ahead and update any base materials that need to be. for (TSet<UMaterial*>::TConstIterator It(BaseMaterialsThatUseThisTexture); It; ++It) { UpdateContext.AddMaterial(*It); (*It)->PostEditChange(); } } // Now that all materials and instances have updated send necessary callbacks. for (int32 i = 0; i < MaterialsThatUseThisTexture.Num(); ++i) { FEditorSupportDelegates::MaterialTextureSettingsChanged.Broadcast(MaterialsThatUseThisTexture[i]); } } #if WITH_EDITORONLY_DATA // any texture that is referencing this texture as AssociatedNormalMap needs to be informed { TArray<UTexture*> TexturesThatUseThisTexture; for (TObjectIterator<UTexture> It; It; ++It) { UTexture* Tex = *It; if(Tex != this && Tex->CompositeTexture == this && Tex->CompositeTextureMode != CTM_Disabled) { TexturesThatUseThisTexture.Add(Tex); } } for (int32 i = 0; i < TexturesThatUseThisTexture.Num(); ++i) { TexturesThatUseThisTexture[i]->PostEditChange(); } } #endif }
void UTexture::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); SetLightingGuid(); // Determine whether any property that requires recompression of the texture, or notification to Materials has changed. bool RequiresNotifyMaterials = false; bool DeferCompressionWasEnabled = false; UProperty* PropertyThatChanged = PropertyChangedEvent.Property; if( PropertyThatChanged ) { static const FName CompressionSettingsName("CompressionSettings"); static const FName LODGroupName("LODGroup"); static const FName DeferCompressionName("DeferCompression"); #if WITH_EDITORONLY_DATA static const FName MaxTextureSizeName("MaxTextureSize"); #endif // #if WITH_EDITORONLY_DATA const FName PropertyName = PropertyThatChanged->GetFName(); if (PropertyName == CompressionSettingsName || PropertyName == LODGroupName) { RequiresNotifyMaterials = true; } else if (PropertyName == DeferCompressionName) { DeferCompressionWasEnabled = DeferCompression; } #if WITH_EDITORONLY_DATA else if (PropertyName == MaxTextureSizeName) { if (MaxTextureSize <= 0) { MaxTextureSize = 0; } else { MaxTextureSize = FMath::Min<int32>(FMath::RoundUpToPowerOfTwo(MaxTextureSize), GetMaximumDimension()); } } #endif // #if WITH_EDITORONLY_DATA bool bPreventSRGB = (CompressionSettings == TC_Alpha || CompressionSettings == TC_Normalmap || CompressionSettings == TC_Masks || CompressionSettings == TC_HDR || CompressionSettings == TC_HDR_Compressed); if(bPreventSRGB && SRGB == true) { SRGB = false; } } else { FMaterialUpdateContext UpdateContext; // Update any material that uses this texture TSet<UMaterial*> BaseMaterialsThatUseThisTexture; for (TObjectIterator<UMaterialInterface> It; It; ++It) { UMaterialInterface* MaterialInterface = *It; if (DoesMaterialUseTexture(MaterialInterface, this)) { UMaterial *Material = MaterialInterface->GetMaterial(); bool MaterialAlreadyCompute = false; BaseMaterialsThatUseThisTexture.Add(Material, &MaterialAlreadyCompute); if (!MaterialAlreadyCompute) { UpdateContext.AddMaterial(Material); if (Material->IsTextureForceRecompileCacheRessource(this)) { Material->UpdateMaterialShaderCacheAndTextureReferences(); } } } } //If the DDC key was different the material is already recompile here RequiresNotifyMaterials = false; } NumCinematicMipLevels = FMath::Max<int32>( NumCinematicMipLevels, 0 ); // Don't update the texture resource if we've turned "DeferCompression" on, as this // would cause it to immediately update as an uncompressed texture if( !DeferCompressionWasEnabled && (PropertyChangedEvent.ChangeType & EPropertyChangeType::Interactive) == 0 ) { // Update the texture resource. This will recache derived data if necessary // which may involve recompressing the texture. UpdateResource(); } // Notify any loaded material instances if changed our compression format if (RequiresNotifyMaterials) { TArray<UMaterialInterface*> MaterialsThatUseThisTexture; // Create a material update context to safely update materials. { FMaterialUpdateContext UpdateContext; // Notify any material that uses this texture TSet<UMaterial*> BaseMaterialsThatUseThisTexture; for (TObjectIterator<UMaterialInterface> It; It; ++It) { UMaterialInterface* MaterialInterface = *It; if (DoesMaterialUseTexture(MaterialInterface,this)) { MaterialsThatUseThisTexture.Add(MaterialInterface); // This is a bit tricky. We want to make sure all materials using this texture are // updated. Materials are always updated. Material instances may also have to be // updated and if they have static permutations their children must be updated // whether they use the texture or not! The safe thing to do is to add the instance's // base material to the update context causing all materials in the tree to update. BaseMaterialsThatUseThisTexture.Add(MaterialInterface->GetMaterial()); } } // Go ahead and update any base materials that need to be. for (TSet<UMaterial*>::TConstIterator It(BaseMaterialsThatUseThisTexture); It; ++It) { UpdateContext.AddMaterial(*It); (*It)->PostEditChange(); } } // Now that all materials and instances have updated send necessary callbacks. for (int32 i = 0; i < MaterialsThatUseThisTexture.Num(); ++i) { FEditorSupportDelegates::MaterialTextureSettingsChanged.Broadcast(MaterialsThatUseThisTexture[i]); } } #if WITH_EDITORONLY_DATA // any texture that is referencing this texture as AssociatedNormalMap needs to be informed { TArray<UTexture*> TexturesThatUseThisTexture; for (TObjectIterator<UTexture> It; It; ++It) { UTexture* Tex = *It; if(Tex != this && Tex->CompositeTexture == this && Tex->CompositeTextureMode != CTM_Disabled) { TexturesThatUseThisTexture.Add(Tex); } } for (int32 i = 0; i < TexturesThatUseThisTexture.Num(); ++i) { TexturesThatUseThisTexture[i]->PostEditChange(); } } #endif }