void FRawMeshBulkData::LoadRawMesh(FRawMesh& OutMesh) { OutMesh.Empty(); if (BulkData.GetElementCount() > 0) { FBufferReader Ar( BulkData.Lock(LOCK_READ_ONLY), BulkData.GetElementCount(), /*bInFreeOnClose=*/ false, /*bIsPersistent=*/ true ); Ar << OutMesh; BulkData.Unlock(); } }
/** * Creates a D3DXMESH from a FStaticMeshRenderData * @param Triangles The triangles to create the mesh from. * @param bRemoveDegenerateTriangles True if degenerate triangles should be removed * @param OutD3DMesh Mesh to create * @return Boolean representing success or failure */ bool ConvertRawMeshToD3DXMesh( IDirect3DDevice9* Device, FRawMesh& RawMesh, const bool bRemoveDegenerateTriangles, TRefCountPtr<ID3DXMesh>& OutD3DMesh ) { TArray<D3DVERTEXELEMENT9> VertexElements; GetD3D9MeshVertexDeclarations(VertexElements); TArray<FUtilVertex> Vertices; TArray<uint16> Indices; TArray<uint32> Attributes; int32 NumWedges = RawMesh.WedgeIndices.Num(); int32 NumTriangles = NumWedges / 3; int32 NumUVs = 0; for (; NumUVs < 8; ++NumUVs) { if (RawMesh.WedgeTexCoords[NumUVs].Num() != RawMesh.WedgeIndices.Num()) { break; } } bool bHaveColors = RawMesh.WedgeColors.Num() == NumWedges; bool bHaveNormals = RawMesh.WedgeTangentZ.Num() == NumWedges; bool bHaveTangents = bHaveNormals && RawMesh.WedgeTangentX.Num() == NumWedges && RawMesh.WedgeTangentY.Num() == NumWedges; for(int32 TriangleIndex = 0;TriangleIndex < NumTriangles;TriangleIndex++) { bool bTriangleIsDegenerate = false; if( bRemoveDegenerateTriangles ) { // Detect if the triangle is degenerate. for(int32 EdgeIndex = 0;EdgeIndex < 3;EdgeIndex++) { const int32 Wedge0 = TriangleIndex * 3 + EdgeIndex; const int32 Wedge1 = TriangleIndex * 3 + ((EdgeIndex + 1) % 3); if((RawMesh.GetWedgePosition(Wedge0) - RawMesh.GetWedgePosition(Wedge1)).IsNearlyZero(THRESH_POINTS_ARE_SAME * 4.0f)) { bTriangleIsDegenerate = true; break; } } } if(!bTriangleIsDegenerate) { Attributes.Add(RawMesh.FaceMaterialIndices[TriangleIndex]); for(int32 J=0;J<3;J++) { FUtilVertex* Vertex = new(Vertices) FUtilVertex; FMemory::Memzero(Vertex,sizeof(FUtilVertex)); int32 WedgeIndex = TriangleIndex * 3 + J; Vertex->Position = RawMesh.GetWedgePosition(WedgeIndex); if (bHaveColors) { Vertex->Color = RawMesh.WedgeColors[WedgeIndex]; } else { Vertex->Color = FColor::White; } //store the smoothing mask per vertex since there is only one per-face attribute that is already being used (materialIndex) Vertex->SmoothingMask = RawMesh.FaceSmoothingMasks[TriangleIndex]; if (bHaveTangents) { Vertex->TangentX = RawMesh.WedgeTangentX[WedgeIndex]; Vertex->TangentY = RawMesh.WedgeTangentY[WedgeIndex]; } if (bHaveNormals) { Vertex->TangentZ = RawMesh.WedgeTangentZ[WedgeIndex]; } for(int32 UVIndex = 0; UVIndex < NumUVs; UVIndex++) { Vertex->UVs[UVIndex] = RawMesh.WedgeTexCoords[UVIndex][WedgeIndex]; } Indices.Add(Vertices.Num() - 1); } } } // This code uses the raw triangles. Needs welding, etc. const int32 NumFaces = Indices.Num() / 3; const int32 NumVertices = NumFaces*3; check(Attributes.Num() == NumFaces); check(NumFaces * 3 == Indices.Num()); // Create mesh for source data if (FAILED(D3DXCreateMesh(NumFaces,NumVertices,D3DXMESH_SYSTEMMEM,(D3DVERTEXELEMENT9 *)VertexElements.GetData(),Device,OutD3DMesh.GetInitReference()) ) ) { UE_LOG(LogD3D9MeshUtils, Warning, TEXT("D3DXCreateMesh() Failed!")); return false; } // Fill D3DMesh mesh FUtilVertex* D3DVertices; uint16* D3DIndices; ::DWORD * D3DAttributes; OutD3DMesh->LockVertexBuffer(0,(LPVOID*)&D3DVertices); OutD3DMesh->LockIndexBuffer(0,(LPVOID*)&D3DIndices); OutD3DMesh->LockAttributeBuffer(0, &D3DAttributes); FMemory::Memcpy(D3DVertices,Vertices.GetTypedData(),Vertices.Num() * sizeof(FUtilVertex)); FMemory::Memcpy(D3DIndices,Indices.GetTypedData(),Indices.Num() * sizeof(uint16)); FMemory::Memcpy(D3DAttributes,Attributes.GetTypedData(),Attributes.Num() * sizeof(uint32)); OutD3DMesh->UnlockIndexBuffer(); OutD3DMesh->UnlockVertexBuffer(); OutD3DMesh->UnlockAttributeBuffer(); return true; }
/** * Constructs a raw mesh from legacy render data. */ static void BuildRawMeshFromRenderData( FRawMesh& OutRawMesh, struct FMeshBuildSettings& OutBuildSettings, FLegacyStaticMeshRenderData& RenderData, const TCHAR* MeshName ) { FStaticMeshTriangle* RawTriangles = (FStaticMeshTriangle*)RenderData.RawTriangles.Lock(LOCK_READ_ONLY); bool bBuiltFromRawTriangles = BuildRawMeshFromRawTriangles( OutRawMesh, OutBuildSettings, RawTriangles, RenderData.RawTriangles.GetElementCount(), MeshName ); RenderData.RawTriangles.Unlock(); if (bBuiltFromRawTriangles) { return; } OutRawMesh.Empty(); FIndexArrayView Indices = RenderData.IndexBuffer.GetArrayView(); int32 NumVertices = RenderData.PositionVertexBuffer.GetNumVertices(); int32 NumTriangles = Indices.Num() / 3; int32 NumWedges = NumTriangles * 3; // Copy vertex positions. { OutRawMesh.VertexPositions.AddUninitialized(NumVertices); for (int32 i = 0; i < NumVertices; ++i) { OutRawMesh.VertexPositions[i] = RenderData.PositionVertexBuffer.VertexPosition(i); } } // Copy per-wedge texture coordinates. for (uint32 TexCoordIndex = 0; TexCoordIndex < RenderData.VertexBuffer.GetNumTexCoords(); ++TexCoordIndex) { OutRawMesh.WedgeTexCoords[TexCoordIndex].AddUninitialized(NumWedges); for (int32 i = 0; i < NumWedges; ++i) { uint32 VertIndex = Indices[i]; OutRawMesh.WedgeTexCoords[TexCoordIndex][i] = RenderData.VertexBuffer.GetVertexUV(VertIndex, TexCoordIndex); } } // Copy per-wedge colors if they exist. if (RenderData.ColorVertexBuffer.GetNumVertices() > 0) { OutRawMesh.WedgeColors.AddUninitialized(NumWedges); for (int32 i = 0; i < NumWedges; ++i) { uint32 VertIndex = Indices[i]; OutRawMesh.WedgeColors[i] = RenderData.ColorVertexBuffer.VertexColor(VertIndex); } } // Copy per-wedge tangents. { OutRawMesh.WedgeTangentX.AddUninitialized(NumWedges); OutRawMesh.WedgeTangentY.AddUninitialized(NumWedges); OutRawMesh.WedgeTangentZ.AddUninitialized(NumWedges); for (int32 i = 0; i < NumWedges; ++i) { uint32 VertIndex = Indices[i]; OutRawMesh.WedgeTangentX[i] = RenderData.VertexBuffer.VertexTangentX(VertIndex); OutRawMesh.WedgeTangentY[i] = RenderData.VertexBuffer.VertexTangentY(VertIndex); OutRawMesh.WedgeTangentZ[i] = RenderData.VertexBuffer.VertexTangentZ(VertIndex); } } // Copy per-face information. { OutRawMesh.FaceMaterialIndices.AddZeroed(NumTriangles); OutRawMesh.FaceSmoothingMasks.AddZeroed(NumTriangles); OutRawMesh.WedgeIndices.AddZeroed(NumWedges); for (int32 SectionIndex = 0; SectionIndex < RenderData.Elements.Num(); ++SectionIndex) { const FLegacyStaticMeshElement& Section = RenderData.Elements[SectionIndex]; int32 FirstFace = Section.FirstIndex / 3; int32 LastFace = FirstFace + Section.NumTriangles; for (int32 i = FirstFace; i < LastFace; ++i) { OutRawMesh.FaceMaterialIndices[i] = Section.MaterialIndex; // Smoothing group information has been lost but is already baked in to the tangent basis. for (int32 j = 0; j < 3; ++j) { OutRawMesh.WedgeIndices[i * 3 + j] = Indices[i * 3 + j]; } } } } check(OutRawMesh.IsValid()); OutBuildSettings.bRecomputeNormals = false; OutBuildSettings.bRecomputeTangents = false; OutBuildSettings.bRemoveDegenerates = false; OutBuildSettings.bUseFullPrecisionUVs = RenderData.VertexBuffer.GetUseFullPrecisionUVs(); }
/** * Constructs a raw mesh from legacy raw triangles. */ static bool BuildRawMeshFromRawTriangles( FRawMesh& OutRawMesh, FMeshBuildSettings& OutBuildSettings, struct FStaticMeshTriangle* RawTriangles, int32 NumTriangles, const TCHAR* MeshName ) { int32 NumWedges = NumTriangles * 3; OutRawMesh.Empty(); OutRawMesh.FaceMaterialIndices.Empty(NumTriangles); OutRawMesh.FaceSmoothingMasks.Empty(NumWedges); OutRawMesh.VertexPositions.Empty(NumWedges); OutRawMesh.WedgeIndices.Empty(NumWedges); int32 NumTexCoords = 0; bool bHaveNormals = false; bool bHaveTangents = false; bool bHaveColors = false; bool bCorruptFlags = false; bool bCorruptNormals = false; bool bCorruptTangents = false; bool bCorruptPositions = false; for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { FStaticMeshTriangle* Tri = RawTriangles + TriIndex; OutRawMesh.FaceMaterialIndices.Add(Tri->MaterialIndex); OutRawMesh.FaceSmoothingMasks.Add(Tri->SmoothingMask); bCorruptFlags |= (RawTriangles[0].bExplicitNormals & 0xFFFFFFFE) != 0 || (RawTriangles[0].bOverrideTangentBasis & 0xFFFFFFFE) != 0; for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { FVector Position = Tri->Vertices[CornerIndex]; OutRawMesh.WedgeIndices.Add(OutRawMesh.VertexPositions.Add(Position)); NumTexCoords = FMath::Max(NumTexCoords, Tri->NumUVs); bHaveNormals |= Tri->TangentZ[CornerIndex] != FVector::ZeroVector; bHaveTangents |= (Tri->TangentX[CornerIndex] != FVector::ZeroVector && Tri->TangentY[CornerIndex] != FVector::ZeroVector); bHaveColors |= Tri->Colors[CornerIndex] != FColor::White; bCorruptPositions |= Position.ContainsNaN(); bCorruptTangents |= Tri->TangentX[CornerIndex].ContainsNaN() || Tri->TangentY[CornerIndex].ContainsNaN(); bCorruptNormals |= Tri->TangentZ[CornerIndex].ContainsNaN(); } } bool bTooManyTexCoords = NumTexCoords > 8; NumTexCoords = FMath::Min(NumTexCoords, 8); // FStaticMeshTriangle has at most 8 texture coordinates. NumTexCoords = FMath::Min<int32>(NumTexCoords, MAX_MESH_TEXTURE_COORDS); for (int32 TexCoordIndex = 0; TexCoordIndex < FMath::Max(1, NumTexCoords); ++TexCoordIndex) { OutRawMesh.WedgeTexCoords[TexCoordIndex].AddZeroed(NumTriangles * 3); } for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { FStaticMeshTriangle* Tri = RawTriangles + TriIndex; for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { for (int32 TexCoordIndex = 0; TexCoordIndex < NumTexCoords; ++TexCoordIndex) { FVector2D UV = TexCoordIndex < Tri->NumUVs ? Tri->UVs[CornerIndex][TexCoordIndex] : FVector2D(0.0f,0.0f); OutRawMesh.WedgeTexCoords[TexCoordIndex][TriIndex * 3 + CornerIndex] = UV; } } } if (bHaveNormals && !bCorruptNormals) { OutRawMesh.WedgeTangentZ.AddZeroed(NumTriangles * 3); for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { if (RawTriangles[TriIndex].bExplicitNormals || RawTriangles[TriIndex].bOverrideTangentBasis) { for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { OutRawMesh.WedgeTangentZ[TriIndex * 3 + CornerIndex] = RawTriangles[TriIndex].TangentZ[CornerIndex]; } } } } if (bHaveTangents && !bCorruptTangents) { OutRawMesh.WedgeTangentX.AddZeroed(NumTriangles * 3); OutRawMesh.WedgeTangentY.AddZeroed(NumTriangles * 3); for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { if (RawTriangles[TriIndex].bOverrideTangentBasis) { for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { OutRawMesh.WedgeTangentX[TriIndex * 3 + CornerIndex] = RawTriangles[TriIndex].TangentX[CornerIndex]; OutRawMesh.WedgeTangentY[TriIndex * 3 + CornerIndex] = RawTriangles[TriIndex].TangentY[CornerIndex]; } } } } if (bHaveColors) { OutRawMesh.WedgeColors.AddUninitialized(NumTriangles * 3); for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { OutRawMesh.WedgeColors[TriIndex * 3 + CornerIndex] = RawTriangles[TriIndex].Colors[CornerIndex]; } } } if (bCorruptPositions || bCorruptFlags || bCorruptNormals || bCorruptTangents || bTooManyTexCoords) { UE_LOG(LogStaticMesh,Verbose,TEXT("Legacy raw triangles CORRUPT (%s%s%s%s%s) for %s"), bCorruptPositions ? TEXT("NaN positions,") : TEXT(""), bCorruptFlags ? TEXT("flags,") : TEXT(""), bCorruptNormals ? TEXT("NaN normals,") : TEXT(""), bCorruptTangents ? TEXT("NaN tangents,") : TEXT(""), bTooManyTexCoords ? TEXT(">8 texcoords") : TEXT(""), MeshName ); } if (bCorruptPositions) { OutRawMesh = FRawMesh(); } OutBuildSettings.bRecomputeTangents = !bHaveTangents || bCorruptTangents; OutBuildSettings.bRecomputeNormals = !bHaveNormals || bCorruptNormals; return !(bCorruptPositions || bCorruptFlags || bTooManyTexCoords) && OutRawMesh.IsValid(); }
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(); }