FName FActorFolders::GetDefaultFolderNameForSelection(UWorld& InWorld) { // Find a common parent folder, or put it at the root FName CommonParentFolder; for( FSelectionIterator SelectionIt( *GEditor->GetSelectedActors() ); SelectionIt; ++SelectionIt ) { AActor* Actor = CastChecked<AActor>(*SelectionIt); if (CommonParentFolder.IsNone()) { CommonParentFolder = Actor->GetFolderPath(); } else if (Actor->GetFolderPath() != CommonParentFolder) { CommonParentFolder = NAME_None; break; } } return GetDefaultFolderName(InWorld, CommonParentFolder); }
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(); }