Пример #1
0
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();
	}
}
Пример #2
0
/**
	* 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;
}
Пример #3
0
/**
 * 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();
}
Пример #4
0
/**
 * 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();
}