You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
3315 lines
134 KiB
C++
3315 lines
134 KiB
C++
|
|
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
||
|
|
|
||
|
|
#include "EditableStaticMesh.h"
|
||
|
|
#include "EditableMeshChanges.h"
|
||
|
|
#include "Engine/StaticMesh.h"
|
||
|
|
#include "Components/StaticMeshComponent.h"
|
||
|
|
#include "StaticMeshResources.h"
|
||
|
|
#include "PhysicsEngine/BodySetup.h" // For collision generation
|
||
|
|
#include "ProfilingDebugging/ScopedTimers.h" // For FAutoScopedDurationTimer
|
||
|
|
#include "EditableMeshFactory.h"
|
||
|
|
|
||
|
|
|
||
|
|
const FRenderingVertexID FRenderingVertexID::Invalid( TNumericLimits<uint32>::Max() );
|
||
|
|
const FTriangleID FTriangleID::Invalid( TNumericLimits<uint32>::Max() );
|
||
|
|
|
||
|
|
|
||
|
|
UEditableStaticMesh::UEditableStaticMesh()
|
||
|
|
: UEditableMesh(),
|
||
|
|
StaticMesh( nullptr ),
|
||
|
|
Vertices(),
|
||
|
|
Edges(),
|
||
|
|
Sections(),
|
||
|
|
RecreateRenderStateContext(),
|
||
|
|
PendingCompactCounter( 0 )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
inline void UEditableStaticMesh::EnsureIndexBufferIs32Bit()
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
if( !StaticMeshLOD.IndexBuffer.Is32Bit() )
|
||
|
|
{
|
||
|
|
// Need a 32-bit index buffer
|
||
|
|
static TArray<uint32> AllIndices;
|
||
|
|
StaticMeshLOD.IndexBuffer.GetCopy( /* Out */ AllIndices );
|
||
|
|
StaticMeshLOD.IndexBuffer.SetIndices( AllIndices, EIndexBufferStride::Force32Bit );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
inline void UEditableStaticMesh::UpdateIndexBufferFormatIfNeeded( const TArray<FRenderingVertexID>& RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
check( !IsPreviewingSubdivisions() ); // Should not be mutating the actual mesh when in subdivision preview mode
|
||
|
|
|
||
|
|
const FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
if( !StaticMeshLOD.IndexBuffer.Is32Bit() )
|
||
|
|
{
|
||
|
|
for( const FRenderingVertexID RenderingVertexID : RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
if( RenderingVertexID.GetValue() > TNumericLimits<uint16>::Max() )
|
||
|
|
{
|
||
|
|
EnsureIndexBufferIs32Bit();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
inline void UEditableStaticMesh::UpdateIndexBufferFormatIfNeeded( const FRenderingVertexID RenderingVertexID )
|
||
|
|
{
|
||
|
|
if( RenderingVertexID.GetValue() > TNumericLimits<uint16>::Max() )
|
||
|
|
{
|
||
|
|
EnsureIndexBufferIs32Bit();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::InitEditableStaticMesh( class UPrimitiveComponent& Component, const FEditableMeshSubMeshAddress& InitSubMeshAddress )
|
||
|
|
{
|
||
|
|
SetSubMeshAddress( InitSubMeshAddress );
|
||
|
|
|
||
|
|
// We're partial to static mesh components, here
|
||
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>( &Component );
|
||
|
|
if( StaticMeshComponent != nullptr )
|
||
|
|
{
|
||
|
|
UStaticMesh* ComponentStaticMesh = StaticMeshComponent->GetStaticMesh();
|
||
|
|
if( ComponentStaticMesh != nullptr && ComponentStaticMesh->HasValidRenderData() )
|
||
|
|
{
|
||
|
|
this->StaticMesh = ComponentStaticMesh;
|
||
|
|
this->OriginalStaticMesh = ComponentStaticMesh;
|
||
|
|
|
||
|
|
const FStaticMeshRenderData& StaticMeshRenderData = *StaticMesh->RenderData;
|
||
|
|
if( SubMeshAddress.LODIndex >= 0 && SubMeshAddress.LODIndex < StaticMeshRenderData.LODResources.Num() )
|
||
|
|
{
|
||
|
|
{
|
||
|
|
// @todo mesheditor urgent: Currently, we're disabling many of the optimized index buffers that were precomputed
|
||
|
|
// for static meshes when they become editable. This is just so that we don't have to keep this data up to
|
||
|
|
// date as we perform live edits to the geometry. Later, we should probably get this updated as we go, or
|
||
|
|
// lazily update the buffers when committing a final change or saving. Without clearing these values, some
|
||
|
|
// graphical artifacts will be visible while editing the mesh (flickering shadows, for example.)
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = StaticMesh->RenderData->LODResources[ SubMeshAddress.LODIndex ];
|
||
|
|
StaticMeshLOD.bHasAdjacencyInfo = false;
|
||
|
|
StaticMeshLOD.bHasDepthOnlyIndices = false;
|
||
|
|
StaticMeshLOD.bHasReversedIndices = false;
|
||
|
|
StaticMeshLOD.bHasReversedDepthOnlyIndices = false;
|
||
|
|
StaticMeshLOD.DepthOnlyNumTriangles = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
const FStaticMeshLODResources& StaticMeshLOD = StaticMeshRenderData.LODResources[ SubMeshAddress.LODIndex ];
|
||
|
|
|
||
|
|
// Store off the number of texture coordinates in this mesh
|
||
|
|
this->TextureCoordinateCount = StaticMeshLOD.GetNumTexCoords();
|
||
|
|
|
||
|
|
// Vertices
|
||
|
|
const int32 NumRenderingVertices = StaticMeshLOD.PositionVertexBuffer.GetNumVertices();
|
||
|
|
const int32 NumUVs = TextureCoordinateCount;
|
||
|
|
const bool bHasColor = StaticMeshLOD.ColorVertexBuffer.GetNumVertices() > 0;
|
||
|
|
check( !bHasColor || StaticMeshLOD.ColorVertexBuffer.GetNumVertices() == StaticMeshLOD.VertexBuffer.GetNumVertices() );
|
||
|
|
RenderingVertices.Reserve( NumRenderingVertices );
|
||
|
|
|
||
|
|
// @todo mesheditor cleanup: This code is very similar to the static mesh build code; try to share helper structs
|
||
|
|
static TMultiMap<int32, int32> OverlappingRenderingVertexIndices;
|
||
|
|
{
|
||
|
|
OverlappingRenderingVertexIndices.Reset();
|
||
|
|
|
||
|
|
/** Helper struct for building acceleration structures. */
|
||
|
|
struct FIndexAndZ
|
||
|
|
{
|
||
|
|
float Z;
|
||
|
|
int32 Index;
|
||
|
|
|
||
|
|
FIndexAndZ()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Initialization constructor. */
|
||
|
|
FIndexAndZ(int32 InIndex, FVector V)
|
||
|
|
{
|
||
|
|
Z = 0.30f * V.X + 0.33f * V.Y + 0.37f * V.Z;
|
||
|
|
Index = InIndex;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Build a temporary array of rendering vertex indices, sorted by their Z value. This will accelerate
|
||
|
|
// searching through to find duplicates
|
||
|
|
static TArray<FIndexAndZ> RenderingVertexIndicesSortedByZ;
|
||
|
|
{
|
||
|
|
RenderingVertexIndicesSortedByZ.SetNumUninitialized( NumRenderingVertices, false );
|
||
|
|
for( int32 RenderingVertexIndex = 0; RenderingVertexIndex < NumRenderingVertices; ++RenderingVertexIndex )
|
||
|
|
{
|
||
|
|
const FVector VertexPosition = StaticMeshLOD.PositionVertexBuffer.VertexPosition( RenderingVertexIndex );
|
||
|
|
RenderingVertexIndicesSortedByZ[ RenderingVertexIndex ] = FIndexAndZ( RenderingVertexIndex, VertexPosition );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sort the vertices by z value
|
||
|
|
struct FCompareIndexAndZ
|
||
|
|
{
|
||
|
|
FORCEINLINE bool operator()( FIndexAndZ const& A, FIndexAndZ const& B ) const { return A.Z < B.Z; }
|
||
|
|
};
|
||
|
|
RenderingVertexIndicesSortedByZ.Sort( FCompareIndexAndZ() );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Search for duplicates, quickly!
|
||
|
|
const float ComparisonThreshold = KINDA_SMALL_NUMBER; // @todo mesheditor: Tweak "weld" threshold
|
||
|
|
for( int32 RenderingVertexIterA = 0; RenderingVertexIterA < NumRenderingVertices; ++RenderingVertexIterA )
|
||
|
|
{
|
||
|
|
// only need to search forward, since we add pairs both ways
|
||
|
|
for( int32 RenderingVertexIterB = RenderingVertexIterA + 1; RenderingVertexIterB < NumRenderingVertices; ++RenderingVertexIterB )
|
||
|
|
{
|
||
|
|
if( FMath::Abs( RenderingVertexIndicesSortedByZ[ RenderingVertexIterB ].Z - RenderingVertexIndicesSortedByZ[ RenderingVertexIterA ].Z ) > ComparisonThreshold )
|
||
|
|
{
|
||
|
|
break; // can't be any more dups
|
||
|
|
}
|
||
|
|
|
||
|
|
const int32 RenderingVertexIndexA = RenderingVertexIndicesSortedByZ[ RenderingVertexIterA ].Index;
|
||
|
|
const int32 RenderingVertexIndexB = RenderingVertexIndicesSortedByZ[ RenderingVertexIterB ].Index;
|
||
|
|
|
||
|
|
const FVector VertexPositionA = StaticMeshLOD.PositionVertexBuffer.VertexPosition( RenderingVertexIndexA );
|
||
|
|
const FVector VertexPositionB = StaticMeshLOD.PositionVertexBuffer.VertexPosition( RenderingVertexIndexB );
|
||
|
|
|
||
|
|
if( VertexPositionA.Equals( VertexPositionB, ComparisonThreshold ) )
|
||
|
|
{
|
||
|
|
OverlappingRenderingVertexIndices.Add( RenderingVertexIndexA, RenderingVertexIndexB );
|
||
|
|
OverlappingRenderingVertexIndices.Add( RenderingVertexIndexB, RenderingVertexIndexA );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We'll now make sure we have an editable mesh vertex created for every uniquely-positioned rendering vertex.
|
||
|
|
// Note that it's important that we process all vertices, not only the vertices that are referenced by triangles
|
||
|
|
// in the index buffer, because we properly support meshes with vertices that are not yet connected to any
|
||
|
|
// polygons. These vertices will simply not have editable mesh polygons or edges connected to them, but will
|
||
|
|
// still be interactable in the editor.
|
||
|
|
for( int32 RenderingVertexIndex = 0; RenderingVertexIndex < NumRenderingVertices; ++RenderingVertexIndex )
|
||
|
|
{
|
||
|
|
const FVector VertexPosition = StaticMeshLOD.PositionVertexBuffer.VertexPosition( RenderingVertexIndex );
|
||
|
|
|
||
|
|
// Check to see if we already have this vertex
|
||
|
|
bool bAlreadyHaveVertexForPosition = false;
|
||
|
|
{
|
||
|
|
static TArray<int32> ThisRenderingVertexOverlaps;
|
||
|
|
ThisRenderingVertexOverlaps.Reset();
|
||
|
|
OverlappingRenderingVertexIndices.MultiFind( RenderingVertexIndex, /* Out */ ThisRenderingVertexOverlaps );
|
||
|
|
|
||
|
|
for( int32 OverlappingRenderingVertexIter = 0; OverlappingRenderingVertexIter < ThisRenderingVertexOverlaps.Num(); ++OverlappingRenderingVertexIter )
|
||
|
|
{
|
||
|
|
const int32 OverlappingRenderingVertexIndex = ThisRenderingVertexOverlaps[ OverlappingRenderingVertexIter ];
|
||
|
|
|
||
|
|
// If the overlapping rendering vertex index is smaller than our current index, we can safely assume that
|
||
|
|
// we've already processed this vertex position and created an editable mesh vertex for it.
|
||
|
|
if( OverlappingRenderingVertexIndex < RenderingVertexIndex )
|
||
|
|
{
|
||
|
|
check( RenderingVertices.IsAllocated( OverlappingRenderingVertexIndex ) );
|
||
|
|
const FVertexID ExistingVertexID = RenderingVertices[ OverlappingRenderingVertexIndex ].VertexID;
|
||
|
|
FEditableStaticMeshVertex& ExistingVertex = Vertices[ ExistingVertexID.GetValue() ];
|
||
|
|
|
||
|
|
// We already have a unique editable vertex for this rendering vertex position, so link them!
|
||
|
|
RenderingVertices.InsertUninitialized( RenderingVertexIndex );
|
||
|
|
RenderingVertices[ RenderingVertexIndex ].VertexID = ExistingVertexID;
|
||
|
|
|
||
|
|
const FRenderingVertexID RenderingVertexID( RenderingVertexIndex );
|
||
|
|
checkSlow( !ExistingVertex.RenderingVertexIDs.Contains( RenderingVertexID ) );
|
||
|
|
ExistingVertex.RenderingVertexIDs.Add( RenderingVertexID );
|
||
|
|
bAlreadyHaveVertexForPosition = true;
|
||
|
|
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !bAlreadyHaveVertexForPosition )
|
||
|
|
{
|
||
|
|
const FVertexID NewVertexID( Vertices.Add( FEditableStaticMeshVertex() ) );
|
||
|
|
RenderingVertices.InsertUninitialized( RenderingVertexIndex );
|
||
|
|
RenderingVertices[ RenderingVertexIndex ].VertexID = NewVertexID;
|
||
|
|
FEditableStaticMeshVertex& NewVertex = Vertices[ NewVertexID.GetValue() ];
|
||
|
|
NewVertex.VertexPosition = VertexPosition;
|
||
|
|
|
||
|
|
NewVertex.CornerSharpness = 0.0f;
|
||
|
|
|
||
|
|
// @todo mesheditor: If a mesh somehow contained rendering vertices that no triangle was referencing, this would cause
|
||
|
|
// the rendering vertex to be ignored by the editable mesh code. It would just sit in the vertex buffer (and in the
|
||
|
|
// editable mesh vertex's RenderingVertexIndices list), but would never be touched. The editable mesh code only
|
||
|
|
// creates rendering vertices for vertices that are attached to polygons, so this should never happen with meshes
|
||
|
|
// that we create and save. Only if the incoming data had orphan vertices in it. Should hopefully not be a problem.
|
||
|
|
const FRenderingVertexID RenderingVertexID( RenderingVertexIndex );
|
||
|
|
NewVertex.RenderingVertexIDs.Add( RenderingVertexID );
|
||
|
|
|
||
|
|
// NOTE: The new vertex's connected polygons will be filled in down below, as we're processing mesh triangles
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
const FIndexArrayView RenderingIndices = StaticMeshLOD.IndexBuffer.GetArrayView();
|
||
|
|
|
||
|
|
|
||
|
|
static TMap<uint64, FEdgeID> UniqueEdgeToEdgeID;
|
||
|
|
UniqueEdgeToEdgeID.Reset();
|
||
|
|
|
||
|
|
// Add all sections
|
||
|
|
const uint32 NumSections = StaticMeshLOD.Sections.Num();
|
||
|
|
for( uint32 RenderingSectionIndex = 0; RenderingSectionIndex < NumSections; ++RenderingSectionIndex )
|
||
|
|
{
|
||
|
|
const FStaticMeshSection& RenderingSection = StaticMeshLOD.Sections[ RenderingSectionIndex ];
|
||
|
|
|
||
|
|
// Create a new editable mesh section
|
||
|
|
const FSectionID NewSectionID( Sections.Add( FEditableStaticMeshSection() ) );
|
||
|
|
FEditableStaticMeshSection& NewSection = Sections[ NewSectionID.GetValue() ];
|
||
|
|
NewSection.RenderingSectionIndex = RenderingSectionIndex;
|
||
|
|
NewSection.MaterialIndex = RenderingSection.MaterialIndex;
|
||
|
|
NewSection.bEnableCollision = RenderingSection.bEnableCollision;
|
||
|
|
NewSection.bCastShadow = RenderingSection.bCastShadow;
|
||
|
|
|
||
|
|
const uint32 NumSectionTriangles = RenderingSection.NumTriangles;
|
||
|
|
NewSection.Triangles.Reserve( NumSectionTriangles );
|
||
|
|
NewSection.MaxTriangles = NumSectionTriangles;
|
||
|
|
|
||
|
|
for( uint32 SectionTriangleIndex = 0; SectionTriangleIndex < NumSectionTriangles; ++SectionTriangleIndex )
|
||
|
|
{
|
||
|
|
const uint32 RenderingTriangleFirstVertexIndex = FEditableStaticMeshSection::TriangleIndexToRenderingTriangleFirstIndex( RenderingSection, FTriangleID( SectionTriangleIndex ) );
|
||
|
|
|
||
|
|
uint32 TriangleRenderingVertexIndices[ 3 ];
|
||
|
|
FVertexID TriangleVertexIDs[ 3 ];
|
||
|
|
for( uint32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex )
|
||
|
|
{
|
||
|
|
TriangleRenderingVertexIndices[ TriangleVertexIndex ] = RenderingIndices[ RenderingTriangleFirstVertexIndex + TriangleVertexIndex ];
|
||
|
|
TriangleVertexIDs[ TriangleVertexIndex ] = RenderingVertices[ TriangleRenderingVertexIndices[ TriangleVertexIndex ] ].VertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure we have a valid triangle. The triangle can be invalid because at least two if it's vertex indices
|
||
|
|
// point to the exact same vertex. The triangle is degenerate. This can happen due to us welding the overlapping
|
||
|
|
// vertices because they were either extremely close to each other (or exactly overlapping.) We'll ignore this triangle.
|
||
|
|
const bool bIsValidTriangle =
|
||
|
|
TriangleVertexIDs[ 0 ] != TriangleVertexIDs[ 1 ] &&
|
||
|
|
TriangleVertexIDs[ 1 ] != TriangleVertexIDs[ 2 ] &&
|
||
|
|
TriangleVertexIDs[ 2 ] != TriangleVertexIDs[ 0 ];
|
||
|
|
if( bIsValidTriangle )
|
||
|
|
{
|
||
|
|
// Static meshes only support triangles, so there's no need to triangulate anything yet. We'll make both
|
||
|
|
// a triangle and a polygon here.
|
||
|
|
const int32 NewTriangleIndex = SectionTriangleIndex;
|
||
|
|
NewSection.Triangles.InsertUninitialized( NewTriangleIndex );
|
||
|
|
FEditableStaticMeshTriangle& NewTriangle = NewSection.Triangles[ NewTriangleIndex ];
|
||
|
|
|
||
|
|
const FPolygonID NewPolygonID( NewSection.Polygons.Add( FEditableStaticMeshPolygon() ) );
|
||
|
|
FEditableStaticMeshPolygon& NewPolygon = NewSection.Polygons[ NewPolygonID.GetValue() ];
|
||
|
|
NewPolygon.TriangulatedPolygonTriangleIndices.Add( FTriangleID( NewTriangleIndex ) );
|
||
|
|
|
||
|
|
|
||
|
|
// Static meshes don't support polygons with holes, so we always start out with only a perimeter contour per polygon
|
||
|
|
FEditableStaticMeshPolygonContour& PerimeterContour = NewPolygon.PerimeterContour;
|
||
|
|
PerimeterContour.Vertices.Reserve( 3 );
|
||
|
|
|
||
|
|
// Connect vertices
|
||
|
|
for( uint32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex )
|
||
|
|
{
|
||
|
|
const uint32 RenderingVertexIndex = TriangleRenderingVertexIndices[ TriangleVertexIndex ];
|
||
|
|
const FVertexID VertexID = TriangleVertexIDs[ TriangleVertexIndex ];
|
||
|
|
const FRenderingVertexID RenderingVertexID = FRenderingVertexID( RenderingVertexIndex );
|
||
|
|
|
||
|
|
// The triangle points to each of its three vertices
|
||
|
|
NewTriangle.RenderingVertexIDs[ TriangleVertexIndex ] = RenderingVertexID;
|
||
|
|
|
||
|
|
// Tell the polygon contour about this vertex
|
||
|
|
const int32 PolygonContourVertexIndex = PerimeterContour.Vertices.Add( FEditableStaticMeshPolygonContourVertex() );
|
||
|
|
FEditableStaticMeshPolygonContourVertex& PolygonContourVertex = PerimeterContour.Vertices[ PolygonContourVertexIndex ];
|
||
|
|
PolygonContourVertex.VertexID = VertexID;
|
||
|
|
PolygonContourVertex.RenderingVertexID = RenderingVertexID;
|
||
|
|
PolygonContourVertex.VertexUVs.Reserve( NumUVs );
|
||
|
|
for( int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex )
|
||
|
|
{
|
||
|
|
PolygonContourVertex.VertexUVs.Add( StaticMeshLOD.VertexBuffer.GetVertexUV( RenderingVertexIndex, UVIndex ) );
|
||
|
|
}
|
||
|
|
const FVector Normal = StaticMeshLOD.VertexBuffer.VertexTangentZ( RenderingVertexIndex );
|
||
|
|
const FVector Tangent = StaticMeshLOD.VertexBuffer.VertexTangentX( RenderingVertexIndex );
|
||
|
|
const FVector Binormal = StaticMeshLOD.VertexBuffer.VertexTangentY( RenderingVertexIndex );
|
||
|
|
PolygonContourVertex.Normal = Normal;
|
||
|
|
PolygonContourVertex.Tangent = Tangent;
|
||
|
|
PolygonContourVertex.BinormalSign = GetBasisDeterminantSign(Tangent, Binormal, Normal);
|
||
|
|
|
||
|
|
if( bHasColor )
|
||
|
|
{
|
||
|
|
PolygonContourVertex.Color = FLinearColor(StaticMeshLOD.ColorVertexBuffer.VertexColor( RenderingVertexIndex ) );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
PolygonContourVertex.Color = FLinearColor::White;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Connect edges
|
||
|
|
{
|
||
|
|
struct Local
|
||
|
|
{
|
||
|
|
inline static uint64 Make64BitValueForEdge( const FVertexID EdgeVertexID0, const FVertexID EdgeVertexID1 )
|
||
|
|
{
|
||
|
|
return uint64( ( uint64( EdgeVertexID0.GetValue() ) << 32 ) | uint64( EdgeVertexID1.GetValue() ) );
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
// Add the edges of this triangle
|
||
|
|
for( uint32 TriangleEdgeNumber = 0; TriangleEdgeNumber < 3; ++TriangleEdgeNumber )
|
||
|
|
{
|
||
|
|
uint32 EdgeRenderingVertexIndices[ 2 ];
|
||
|
|
EdgeRenderingVertexIndices[ 0 ] = RenderingIndices[ RenderingTriangleFirstVertexIndex + ( TriangleEdgeNumber + 0 ) % 3 ];
|
||
|
|
EdgeRenderingVertexIndices[ 1 ] = RenderingIndices[ RenderingTriangleFirstVertexIndex + ( TriangleEdgeNumber + 1 ) % 3 ];
|
||
|
|
|
||
|
|
FVertexID EdgeVertexIDs[ 2 ];
|
||
|
|
EdgeVertexIDs[ 0 ] = RenderingVertices[ EdgeRenderingVertexIndices[ 0 ] ].VertexID;
|
||
|
|
EdgeVertexIDs[ 1 ] = RenderingVertices[ EdgeRenderingVertexIndices[ 1 ] ].VertexID;
|
||
|
|
|
||
|
|
// Check to see if this edge already exists
|
||
|
|
bool bAlreadyHaveEdge = false;
|
||
|
|
FEdgeID EdgeID = FEdgeID::Invalid;
|
||
|
|
{
|
||
|
|
FEdgeID* FoundEdgeIDPtr = UniqueEdgeToEdgeID.Find( Local::Make64BitValueForEdge( EdgeVertexIDs[ 0 ], EdgeVertexIDs[ 1 ] ) );
|
||
|
|
if( FoundEdgeIDPtr == nullptr )
|
||
|
|
{
|
||
|
|
// Try the other way around
|
||
|
|
FoundEdgeIDPtr = UniqueEdgeToEdgeID.Find( Local::Make64BitValueForEdge( EdgeVertexIDs[ 1 ], EdgeVertexIDs[ 0 ] ) );
|
||
|
|
}
|
||
|
|
if( FoundEdgeIDPtr != nullptr )
|
||
|
|
{
|
||
|
|
bAlreadyHaveEdge = true;
|
||
|
|
EdgeID = *FoundEdgeIDPtr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !bAlreadyHaveEdge )
|
||
|
|
{
|
||
|
|
// Create the new edge. We'll connect it to its polygons later on.
|
||
|
|
CreateEdge_Internal( EdgeVertexIDs[ 0 ], EdgeVertexIDs[ 1 ], TArray<FPolygonRef>(), FEdgeID::Invalid, /* Out */ EdgeID );
|
||
|
|
|
||
|
|
UniqueEdgeToEdgeID.Add( Local::Make64BitValueForEdge( EdgeVertexIDs[ 0 ], EdgeVertexIDs[ 1 ] ), EdgeID );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Each edge will point back to the polygon that its connected to. Remember, an edge can be shared by multiple
|
||
|
|
// polygons, but usually its best if only shared by up to two.
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
Edge.ConnectedPolygons.AddUnique( FPolygonRef( NewSectionID, NewPolygonID ) );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Triangle was not valid. This will result in an empty entry in our Triangles sparse array. Luckily,
|
||
|
|
// the triangle is already degenerate so we don't need to change anything. This triangle index will be
|
||
|
|
// re-used if a new triangle needs to be created during editing
|
||
|
|
// @todo mesheditor: This can cause rendering vertices to be orphaned. Should we delete them?
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Figure out whether each edge is hard or soft by looking at their connected polygons
|
||
|
|
for( FEditableStaticMeshEdge& Edge : Edges )
|
||
|
|
{
|
||
|
|
// Default to a hard edge if we have nothing connected
|
||
|
|
Edge.bIsHardEdge = true;
|
||
|
|
|
||
|
|
// Only edges with at least two polygons connected can possibly be soft
|
||
|
|
if( Edge.ConnectedPolygons.Num() >= 2 )
|
||
|
|
{
|
||
|
|
Edge.bIsHardEdge = false;
|
||
|
|
|
||
|
|
FVector FirstEdgeVertexNormals[ 2 ];
|
||
|
|
for( int32 ConnectedPolygonNumber = 0; ConnectedPolygonNumber < Edge.ConnectedPolygons.Num(); ++ConnectedPolygonNumber )
|
||
|
|
{
|
||
|
|
const FPolygonRef& ConnectedPolygonRef = Edge.ConnectedPolygons[ ConnectedPolygonNumber ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Sections[ ConnectedPolygonRef.SectionID.GetValue() ].Polygons[ ConnectedPolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
bool bFoundEdge0 = false;
|
||
|
|
bool bFoundEdge1 = false;
|
||
|
|
FVector EdgeVertexNormals[ 2 ];
|
||
|
|
for( int32 VertexNumber = 0; VertexNumber < Polygon.PerimeterContour.Vertices.Num(); ++VertexNumber )
|
||
|
|
{
|
||
|
|
// Find the two vertices for this edge
|
||
|
|
const FVertexID VertexID = Polygon.PerimeterContour.Vertices[ VertexNumber ].VertexID;
|
||
|
|
if( VertexID == Edge.VertexIDs[ 0 ] )
|
||
|
|
{
|
||
|
|
EdgeVertexNormals[0] = GetPolygonPerimeterVertexAttribute( ConnectedPolygonRef, VertexNumber, UEditableMeshAttribute::VertexNormal(), 0 );
|
||
|
|
bFoundEdge0 = true;
|
||
|
|
}
|
||
|
|
else if( VertexID == Edge.VertexIDs[ 1 ] )
|
||
|
|
{
|
||
|
|
EdgeVertexNormals[1] = GetPolygonPerimeterVertexAttribute( ConnectedPolygonRef, VertexNumber, UEditableMeshAttribute::VertexNormal(), 0 );
|
||
|
|
bFoundEdge1 = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
check( bFoundEdge0 && bFoundEdge1 );
|
||
|
|
|
||
|
|
if( ConnectedPolygonNumber == 0 )
|
||
|
|
{
|
||
|
|
FirstEdgeVertexNormals[0] = EdgeVertexNormals[0];
|
||
|
|
FirstEdgeVertexNormals[1] = EdgeVertexNormals[1];
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
const float Dot0 = FVector::DotProduct( FirstEdgeVertexNormals[0], EdgeVertexNormals[0] );
|
||
|
|
const float Dot1 = FVector::DotProduct( FirstEdgeVertexNormals[1], EdgeVertexNormals[1] );
|
||
|
|
|
||
|
|
// @todo mesheditor: only make hard edges if they are hard in the original model
|
||
|
|
const float MinDotProductForSoftEdge = 0.94f; // adjacent faces with 20 degrees between them get a soft edge
|
||
|
|
if( Dot0 < MinDotProductForSoftEdge || Dot1 < MinDotProductForSoftEdge )
|
||
|
|
{
|
||
|
|
Edge.bIsHardEdge = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
RefreshOpenSubdiv();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::InitFromBlankStaticMesh( UStaticMesh& InStaticMesh )
|
||
|
|
{
|
||
|
|
StaticMesh = &InStaticMesh;
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename T, typename ElementIDType>
|
||
|
|
static void CompactSparseArrayElements( TSparseArray<T>& Array, TSparseArray<ElementIDType>& IndexRemap )
|
||
|
|
{
|
||
|
|
static_assert( TIsDerivedFrom<ElementIDType, FElementID>::IsDerived, "Remap array type must be derived from FElementID" );
|
||
|
|
|
||
|
|
static TSparseArray<T> NewArray;
|
||
|
|
NewArray.Empty( Array.Num() );
|
||
|
|
|
||
|
|
IndexRemap.Empty( Array.GetMaxIndex() );
|
||
|
|
|
||
|
|
// Add valid elements into a new contiguous sparse array. Note non-const iterator so we can move elements.
|
||
|
|
for( TSparseArray<T>::TIterator It( Array ); It; ++It )
|
||
|
|
{
|
||
|
|
const int32 OldElementIndex = It.GetIndex();
|
||
|
|
|
||
|
|
// @todo mesheditor: implement TSparseArray::Add( ElementType&& ) to save this obscure approach
|
||
|
|
const int32 NewElementIndex = NewArray.Add( T() );
|
||
|
|
NewArray[ NewElementIndex ] = MoveTemp( *It );
|
||
|
|
|
||
|
|
// Provide an O(1) lookup from old index to new index, used when patching up vertex references afterwards
|
||
|
|
IndexRemap.Insert( OldElementIndex, ElementIDType( NewElementIndex ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
Array = MoveTemp( NewArray );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
template <typename T, typename ElementIDType>
|
||
|
|
static void UncompactSparseArrayElements( TSparseArray<T>& Array, const TSparseArray<ElementIDType>& IndexRemap )
|
||
|
|
{
|
||
|
|
static_assert( TIsDerivedFrom<ElementIDType, FElementID>::IsDerived, "Remap array type must be derived from FElementID" );
|
||
|
|
|
||
|
|
static TSparseArray<T> NewArray;
|
||
|
|
NewArray.Empty( IndexRemap.GetMaxIndex() );
|
||
|
|
|
||
|
|
// Add valid elements into a new contiguous sparse array. Note non-const iterator so we can move elements.
|
||
|
|
for( TSparseArray<T>::TIterator It( Array ); It; ++It )
|
||
|
|
{
|
||
|
|
const int32 OldElementIndex = It.GetIndex();
|
||
|
|
|
||
|
|
check( IndexRemap.IsAllocated( OldElementIndex ) );
|
||
|
|
const int32 NewElementIndex = IndexRemap[ OldElementIndex ].GetValue();
|
||
|
|
|
||
|
|
// @todo mesheditor: implement TSparseArray::Insert( ElementType&& ) to save this obscure approach
|
||
|
|
NewArray.Insert( NewElementIndex, T() );
|
||
|
|
NewArray[ NewElementIndex ] = MoveTemp( *It );
|
||
|
|
}
|
||
|
|
|
||
|
|
Array = MoveTemp( NewArray );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
template <typename ElementIDType>
|
||
|
|
static void InvertRemapTable( TSparseArray<ElementIDType>& InvertedRemapTable, const TSparseArray<ElementIDType>& RemapTable )
|
||
|
|
{
|
||
|
|
static_assert( TIsDerivedFrom<ElementIDType, FElementID>::IsDerived, "Remap array type must be derived from FElementID" );
|
||
|
|
|
||
|
|
InvertedRemapTable.Empty( RemapTable.Num() );
|
||
|
|
|
||
|
|
for( TSparseArray<ElementIDType>::TConstIterator It( RemapTable ); It; ++It )
|
||
|
|
{
|
||
|
|
InvertedRemapTable.Insert( It->GetValue(), ElementIDType( It.GetIndex() ) );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class FCompactChange : public FChange
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
|
||
|
|
/** Constructor */
|
||
|
|
FCompactChange()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parent class overrides
|
||
|
|
virtual TUniquePtr<FChange> Execute( UObject* Object ) override
|
||
|
|
{
|
||
|
|
UEditableStaticMesh* EditableStaticMesh = CastChecked<UEditableStaticMesh>( Object );
|
||
|
|
verify( !EditableStaticMesh->AnyChangesToUndo() );
|
||
|
|
EditableStaticMesh->Compact();
|
||
|
|
return EditableStaticMesh->MakeUndo();
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual FString ToString() const override
|
||
|
|
{
|
||
|
|
return FString( TEXT( "Compact" ) );
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
struct FUncompactChangeInput
|
||
|
|
{
|
||
|
|
/** A set of remap tables, specifying how the elements should have their indices remapped */
|
||
|
|
UEditableStaticMesh::FElementIDRemappings ElementIDRemappings;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class FUncompactChange : public FChange
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
|
||
|
|
/** Constructor */
|
||
|
|
FUncompactChange( const FUncompactChangeInput& InitInput )
|
||
|
|
: Input( InitInput )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
FUncompactChange( FUncompactChangeInput&& InitInput )
|
||
|
|
: Input( MoveTemp( InitInput ) )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parent class overrides
|
||
|
|
virtual TUniquePtr<FChange> Execute( UObject* Object ) override
|
||
|
|
{
|
||
|
|
UEditableStaticMesh* EditableStaticMesh = CastChecked<UEditableStaticMesh>( Object );
|
||
|
|
verify( !EditableStaticMesh->AnyChangesToUndo() );
|
||
|
|
EditableStaticMesh->Uncompact( Input.ElementIDRemappings );
|
||
|
|
return EditableStaticMesh->MakeUndo();
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual FString ToString() const override
|
||
|
|
{
|
||
|
|
return FString( TEXT( "Uncompact" ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
|
||
|
|
/** The data we need to make this change */
|
||
|
|
FUncompactChangeInput Input;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::FixUpElementIDs( const FElementIDRemappings& Remappings )
|
||
|
|
{
|
||
|
|
for( FEditableStaticMeshVertex& Vertex : Vertices )
|
||
|
|
{
|
||
|
|
// Fix up rendering vertex index references in vertices array
|
||
|
|
for( FRenderingVertexID& RenderingVertexID : Vertex.RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
RenderingVertexID = Remappings.GetRemappedRenderingVertexID( RenderingVertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix up edge index references in the vertex array
|
||
|
|
for( FEdgeID& EdgeID : Vertex.ConnectedEdgeIDs )
|
||
|
|
{
|
||
|
|
EdgeID = Remappings.GetRemappedEdgeID( EdgeID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix up vertex index references in rendering vertex array
|
||
|
|
for( FEditableStaticMeshRenderingVertex& RenderingVertex : RenderingVertices )
|
||
|
|
{
|
||
|
|
RenderingVertex.VertexID = Remappings.GetRemappedVertexID( RenderingVertex.VertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
for( FEditableStaticMeshEdge& Edge : Edges )
|
||
|
|
{
|
||
|
|
// Fix up vertex index references in Edges array
|
||
|
|
for( int32 Index = 0; Index < 2; Index++ )
|
||
|
|
{
|
||
|
|
Edge.VertexIDs[ Index ] = Remappings.GetRemappedVertexID( Edge.VertexIDs[ Index ] );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix up references to section indices
|
||
|
|
for( FPolygonRef& ConnectedPolygon : Edge.ConnectedPolygons )
|
||
|
|
{
|
||
|
|
ConnectedPolygon = Remappings.GetRemappedPolygonRef( ConnectedPolygon );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for( TSparseArray< FEditableStaticMeshSection >::TIterator It( Sections ); It; ++It )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = *It;
|
||
|
|
FSectionID SectionID( It.GetIndex() );
|
||
|
|
|
||
|
|
for( FEditableStaticMeshPolygon& Polygon : Section.Polygons )
|
||
|
|
{
|
||
|
|
// Fix up references to vertex indices in section polygons' contours
|
||
|
|
for( FEditableStaticMeshPolygonContourVertex& ContourVertex : Polygon.PerimeterContour.Vertices )
|
||
|
|
{
|
||
|
|
ContourVertex.VertexID = Remappings.GetRemappedVertexID( ContourVertex.VertexID );
|
||
|
|
ContourVertex.RenderingVertexID = Remappings.GetRemappedRenderingVertexID( ContourVertex.RenderingVertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
for( FEditableStaticMeshPolygonContour& HoleContour : Polygon.HoleContours )
|
||
|
|
{
|
||
|
|
for( FEditableStaticMeshPolygonContourVertex& ContourVertex : HoleContour.Vertices )
|
||
|
|
{
|
||
|
|
ContourVertex.VertexID = Remappings.GetRemappedVertexID( ContourVertex.VertexID );
|
||
|
|
ContourVertex.RenderingVertexID = Remappings.GetRemappedRenderingVertexID( ContourVertex.RenderingVertexID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix up references to triangle indices
|
||
|
|
for( FTriangleID& TriangleID : Polygon.TriangulatedPolygonTriangleIndices )
|
||
|
|
{
|
||
|
|
TriangleID = Remappings.GetRemappedTriangleID( SectionID, TriangleID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for( FEditableStaticMeshTriangle& Triangle : Section.Triangles )
|
||
|
|
{
|
||
|
|
for( int32 Index = 0; Index < 3; ++Index )
|
||
|
|
{
|
||
|
|
Triangle.RenderingVertexIDs[ Index ] = Remappings.GetRemappedRenderingVertexID( Triangle.RenderingVertexIDs[ Index ] );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// @todo mesheditor: broadcast event with remappings so that any cached element IDs can be fixed up.
|
||
|
|
// Will need to move all the editable mesh structures into UEditableMesh and make them public.
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::InitializeStaticMeshBuildVertex( FStaticMeshBuildVertex& StaticMeshVertex, const FEditableStaticMeshPolygonContourVertex ContourVertex )
|
||
|
|
{
|
||
|
|
StaticMeshVertex.Position = Vertices[ ContourVertex.VertexID.GetValue() ].VertexPosition;
|
||
|
|
StaticMeshVertex.TangentX = ContourVertex.Tangent;
|
||
|
|
StaticMeshVertex.TangentY = FVector::CrossProduct( ContourVertex.Normal, ContourVertex.Tangent ).GetSafeNormal() * ContourVertex.BinormalSign;
|
||
|
|
StaticMeshVertex.TangentZ = ContourVertex.Normal;
|
||
|
|
StaticMeshVertex.Color = ContourVertex.Color.ToFColor( true );
|
||
|
|
for( int32 UVIndex = 0; UVIndex < ContourVertex.VertexUVs.Num(); ++UVIndex )
|
||
|
|
{
|
||
|
|
StaticMeshVertex.UVs[ UVIndex ] = ContourVertex.VertexUVs[ UVIndex ];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RebuildRenderMesh()
|
||
|
|
{
|
||
|
|
if( !IsBeingModified() )
|
||
|
|
{
|
||
|
|
const bool bRefreshBounds = true;
|
||
|
|
const bool bInvalidateLighting = true;
|
||
|
|
RebuildRenderMeshStart( bRefreshBounds, bInvalidateLighting );
|
||
|
|
}
|
||
|
|
|
||
|
|
RebuildRenderMeshInternal();
|
||
|
|
|
||
|
|
if( !IsBeingModified() )
|
||
|
|
{
|
||
|
|
const bool bUpdateCollision = true;
|
||
|
|
RebuildRenderMeshFinish( bUpdateCollision );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RebuildRenderMeshInternal()
|
||
|
|
{
|
||
|
|
// @todo mesheditor urgent subdiv: Saw some editable mesh corruption artifacts when testing subDs in VR
|
||
|
|
|
||
|
|
check( RecreateRenderStateContext.IsValid() );
|
||
|
|
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
|
||
|
|
// Build new vertex buffers
|
||
|
|
static TArray< FStaticMeshBuildVertex > StaticMeshBuildVertices;
|
||
|
|
StaticMeshBuildVertices.Reset();
|
||
|
|
|
||
|
|
static TArray< uint32 > IndexBuffer;
|
||
|
|
IndexBuffer.Reset();
|
||
|
|
|
||
|
|
StaticMeshLOD.Sections.Empty( Sections.Num() );
|
||
|
|
|
||
|
|
bool bHasColor = false;
|
||
|
|
|
||
|
|
if( IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
check( GetSubdivisionCount() > 0 );
|
||
|
|
|
||
|
|
const int32 SectionCount = SubdivisionLimitData.Sections.Num();
|
||
|
|
|
||
|
|
// @todo mesheditor subdiv: Only 2 UVs supported for now, just to avoid having to use a dynamic array per vertex; needs a new data layout, probably (SoA)
|
||
|
|
const int32 SubdivMeshTextureCoordinateCount = FMath::Min( GetTextureCoordinateCount(), 2 );
|
||
|
|
|
||
|
|
// The Sections sparse array mirrors the SubdivisionLimitData sections array;
|
||
|
|
// iterate through it in parallel in order to get the material index and other section properties
|
||
|
|
TSparseArray< FEditableStaticMeshSection >::TConstIterator SectionIt( Sections );
|
||
|
|
check( Sections.Num() == SectionCount );
|
||
|
|
|
||
|
|
for( int32 SectionNumber = 0; SectionNumber < SectionCount; ++SectionNumber )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshSection& Section = *SectionIt;
|
||
|
|
const FSubdivisionLimitSection& SubdivisionSection = SubdivisionLimitData.Sections[ SectionNumber ];
|
||
|
|
|
||
|
|
const int32 SectionTriangleCount = SubdivisionSection.SubdividedQuads.Num() * 2;
|
||
|
|
|
||
|
|
// @todo mesheditor subdiv perf: Ideally, if no topology changed we can just fill vertex data and not touch index buffers
|
||
|
|
const int32 FirstSectionVertexIndex = StaticMeshBuildVertices.Num();
|
||
|
|
StaticMeshBuildVertices.AddUninitialized( SectionTriangleCount * 3 );
|
||
|
|
|
||
|
|
const int32 FirstIndexInSection = IndexBuffer.Num();
|
||
|
|
IndexBuffer.Reserve( IndexBuffer.Num() + SectionTriangleCount * 3 );
|
||
|
|
|
||
|
|
// Create new rendering section
|
||
|
|
StaticMeshLOD.Sections.Add( FStaticMeshSection() );
|
||
|
|
FStaticMeshSection& StaticMeshSection = StaticMeshLOD.Sections.Last();
|
||
|
|
|
||
|
|
StaticMeshSection.FirstIndex = FirstIndexInSection;
|
||
|
|
StaticMeshSection.NumTriangles = SectionTriangleCount;
|
||
|
|
StaticMeshSection.MinVertexIndex = FirstSectionVertexIndex;
|
||
|
|
StaticMeshSection.MaxVertexIndex = FirstSectionVertexIndex + SectionTriangleCount * 3;
|
||
|
|
|
||
|
|
StaticMeshSection.MaterialIndex = Section.MaterialIndex;
|
||
|
|
StaticMeshSection.bEnableCollision = Section.bEnableCollision;
|
||
|
|
StaticMeshSection.bCastShadow = Section.bCastShadow;
|
||
|
|
|
||
|
|
// Fill vertices
|
||
|
|
int32 NextVertexIndex = FirstSectionVertexIndex;
|
||
|
|
for( int32 QuadNumber = 0; QuadNumber < SubdivisionSection.SubdividedQuads.Num(); ++QuadNumber )
|
||
|
|
{
|
||
|
|
const FSubdividedQuad& SubdividedQuad = SubdivisionSection.SubdividedQuads[ QuadNumber ];
|
||
|
|
|
||
|
|
// @todo mesheditor subdiv debug
|
||
|
|
// GWarn->Logf( TEXT( "Q%i V%i: U:%0.2f, V:%0.2f" ), QuadNumber, 0, SubdividedQuad.QuadVertex0.TextureCoordinate0.X, SubdividedQuad.QuadVertex0.TextureCoordinate0.Y );
|
||
|
|
// GWarn->Logf( TEXT( "Q%i V%i: U:%0.2f, V:%0.2f" ), QuadNumber, 1, SubdividedQuad.QuadVertex1.TextureCoordinate0.X, SubdividedQuad.QuadVertex1.TextureCoordinate0.Y );
|
||
|
|
// GWarn->Logf( TEXT( "Q%i V%i: U:%0.2f, V:%0.2f" ), QuadNumber, 2, SubdividedQuad.QuadVertex2.TextureCoordinate0.X, SubdividedQuad.QuadVertex2.TextureCoordinate0.Y );
|
||
|
|
// GWarn->Logf( TEXT( "Q%i V%i: U:%0.2f, V:%0.2f" ), QuadNumber, 3, SubdividedQuad.QuadVertex3.TextureCoordinate0.X, SubdividedQuad.QuadVertex3.TextureCoordinate0.Y );
|
||
|
|
|
||
|
|
for( int32 TriangleNumber = 0; TriangleNumber < 2; ++TriangleNumber )
|
||
|
|
{
|
||
|
|
for( int32 TriangleVertexNumber = 0; TriangleVertexNumber < 3; ++TriangleVertexNumber )
|
||
|
|
{
|
||
|
|
int32 QuadVertexNumber;
|
||
|
|
if( TriangleNumber == 0 )
|
||
|
|
{
|
||
|
|
QuadVertexNumber = ( TriangleVertexNumber == 0 ) ? 0 : ( TriangleVertexNumber == 1 ? 2 : 1 );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
QuadVertexNumber = ( TriangleVertexNumber == 0 ) ? 0 : ( TriangleVertexNumber == 1 ? 3 : 2 );
|
||
|
|
}
|
||
|
|
|
||
|
|
const FSubdividedQuadVertex& QuadVertex = SubdividedQuad.GetQuadVertex( QuadVertexNumber );
|
||
|
|
|
||
|
|
const FVector VertexPosition = SubdivisionLimitData.VertexPositions[ QuadVertex.VertexPositionIndex ];
|
||
|
|
|
||
|
|
FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[ NextVertexIndex ];
|
||
|
|
StaticMeshVertex.Position = VertexPosition;
|
||
|
|
StaticMeshVertex.TangentX = QuadVertex.VertexTangent;
|
||
|
|
StaticMeshVertex.TangentY = QuadVertex.VertexBinormalSign * FVector::CrossProduct( QuadVertex.VertexNormal, QuadVertex.VertexTangent );
|
||
|
|
StaticMeshVertex.TangentZ = QuadVertex.VertexNormal;
|
||
|
|
|
||
|
|
for( int32 UVIndex = 0; UVIndex < SubdivMeshTextureCoordinateCount; ++UVIndex )
|
||
|
|
{
|
||
|
|
StaticMeshVertex.UVs[ UVIndex ] = *( &QuadVertex.TextureCoordinate0 + UVIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
StaticMeshVertex.Color = QuadVertex.VertexColor;
|
||
|
|
if( StaticMeshVertex.Color != FColor::White )
|
||
|
|
{
|
||
|
|
bHasColor = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
IndexBuffer.Add( NextVertexIndex++ );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
++SectionIt;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
StaticMeshBuildVertices.SetNum( RenderingVertices.GetMaxIndex() );
|
||
|
|
|
||
|
|
static TBitArray<> VerticesInitialized;
|
||
|
|
VerticesInitialized.Init( false, RenderingVertices.GetMaxIndex() );
|
||
|
|
|
||
|
|
for( FEditableStaticMeshSection& Section : Sections )
|
||
|
|
{
|
||
|
|
Section.RenderingSectionIndex = StaticMeshLOD.Sections.Num();
|
||
|
|
|
||
|
|
// Create new rendering section
|
||
|
|
StaticMeshLOD.Sections.Add( FStaticMeshSection() );
|
||
|
|
FStaticMeshSection& StaticMeshSection = StaticMeshLOD.Sections.Last();
|
||
|
|
|
||
|
|
StaticMeshSection.FirstIndex = IndexBuffer.Num();
|
||
|
|
StaticMeshSection.NumTriangles = Section.Triangles.GetMaxIndex();
|
||
|
|
check( Section.Triangles.GetMaxIndex() <= Section.MaxTriangles );
|
||
|
|
StaticMeshSection.MaterialIndex = Section.MaterialIndex;
|
||
|
|
StaticMeshSection.bEnableCollision = Section.bEnableCollision;
|
||
|
|
StaticMeshSection.bCastShadow = Section.bCastShadow;
|
||
|
|
|
||
|
|
for( const FEditableStaticMeshPolygon& Polygon : Section.Polygons )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygonContourVertex& ContourVertex : Polygon.PerimeterContour.Vertices )
|
||
|
|
{
|
||
|
|
const int32 RenderingVertexIndex = ContourVertex.RenderingVertexID.GetValue();
|
||
|
|
|
||
|
|
if( !VerticesInitialized[ RenderingVertexIndex ] )
|
||
|
|
{
|
||
|
|
if( ContourVertex.Color != FColor::White )
|
||
|
|
{
|
||
|
|
bHasColor = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[ RenderingVertexIndex ];
|
||
|
|
InitializeStaticMeshBuildVertex( StaticMeshVertex, ContourVertex );
|
||
|
|
VerticesInitialized[ RenderingVertexIndex ] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for( const FEditableStaticMeshPolygonContour& HoleContour : Polygon.HoleContours )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygonContourVertex& ContourVertex : HoleContour.Vertices )
|
||
|
|
{
|
||
|
|
const int32 RenderingVertexIndex = ContourVertex.RenderingVertexID.GetValue();
|
||
|
|
|
||
|
|
if( !VerticesInitialized[ RenderingVertexIndex ] )
|
||
|
|
{
|
||
|
|
if( ContourVertex.Color != FColor::White )
|
||
|
|
{
|
||
|
|
bHasColor = true;
|
||
|
|
}
|
||
|
|
FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[ RenderingVertexIndex ];
|
||
|
|
InitializeStaticMeshBuildVertex( StaticMeshVertex, ContourVertex );
|
||
|
|
VerticesInitialized[ RenderingVertexIndex ] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( Section.Triangles.Num() > 0 )
|
||
|
|
{
|
||
|
|
IndexBuffer.Reserve( IndexBuffer.Num() + Section.Triangles.GetMaxIndex() * 3 );
|
||
|
|
uint32 MinIndex = TNumericLimits< uint32 >::Max();
|
||
|
|
uint32 MaxIndex = TNumericLimits< uint32 >::Min();
|
||
|
|
|
||
|
|
// Find the first valid rendering vertex index, so that we have a value we can use for our degenerates
|
||
|
|
check( Section.Triangles.Num() > 0 );
|
||
|
|
const FRenderingVertexID FirstValidRenderingID = Section.Triangles.CreateConstIterator()->RenderingVertexIDs[ 0 ];
|
||
|
|
|
||
|
|
for( int32 TriangleIndex = 0; TriangleIndex < Section.Triangles.GetMaxIndex(); ++TriangleIndex )
|
||
|
|
{
|
||
|
|
if( Section.Triangles.IsAllocated( TriangleIndex ) )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshTriangle& Triangle = Section.Triangles[ TriangleIndex ];
|
||
|
|
for( int32 TriVert = 0; TriVert < 3; ++TriVert )
|
||
|
|
{
|
||
|
|
const uint32 RenderingVertexIndex = Triangle.RenderingVertexIDs[ TriVert ].GetValue();
|
||
|
|
IndexBuffer.Add( RenderingVertexIndex );
|
||
|
|
MinIndex = FMath::Min( MinIndex, RenderingVertexIndex );
|
||
|
|
MaxIndex = FMath::Max( MaxIndex, RenderingVertexIndex );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
IndexBuffer.Add( FirstValidRenderingID.GetValue() );
|
||
|
|
IndexBuffer.Add( FirstValidRenderingID.GetValue() );
|
||
|
|
IndexBuffer.Add( FirstValidRenderingID.GetValue() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
StaticMeshSection.MinVertexIndex = MinIndex;
|
||
|
|
StaticMeshSection.MaxVertexIndex = MaxIndex;
|
||
|
|
|
||
|
|
// Add any index buffer padding.
|
||
|
|
// This can be necessary if we have just loaded an editable mesh which had a MaxTriangles count in the editable mesh section
|
||
|
|
// greater than the sparse array max size (i.e. an extra gap had been reserved for tris).
|
||
|
|
const int32 IndexBufferPadding = Section.MaxTriangles - Section.Triangles.GetMaxIndex();
|
||
|
|
if( IndexBufferPadding > 0 )
|
||
|
|
{
|
||
|
|
IndexBuffer.AddZeroed( IndexBufferPadding * 3 );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// No triangles in this section
|
||
|
|
StaticMeshSection.MinVertexIndex = 0;
|
||
|
|
StaticMeshSection.MaxVertexIndex = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Figure out which index buffer stride we need
|
||
|
|
bool bNeeds32BitIndices = false;
|
||
|
|
for( const FStaticMeshSection& StaticMeshSection : StaticMeshLOD.Sections )
|
||
|
|
{
|
||
|
|
if( StaticMeshSection.MaxVertexIndex > TNumericLimits<uint16>::Max() )
|
||
|
|
{
|
||
|
|
bNeeds32BitIndices = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
|
||
|
|
|
||
|
|
StaticMeshLOD.PositionVertexBuffer.Init( StaticMeshBuildVertices );
|
||
|
|
StaticMeshLOD.VertexBuffer.Init( StaticMeshBuildVertices, GetTextureCoordinateCount() );
|
||
|
|
|
||
|
|
if( bHasColor )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.Init( StaticMeshBuildVertices );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.InitFromSingleColor( FColor::White, StaticMeshBuildVertices.Num() );
|
||
|
|
}
|
||
|
|
|
||
|
|
StaticMeshLOD.IndexBuffer.SetIndices( IndexBuffer, IndexBufferStride );
|
||
|
|
|
||
|
|
// @todo mesheditor: support the other index buffer types
|
||
|
|
StaticMeshLOD.ReversedIndexBuffer.SetIndices( TArray< uint32 >(), IndexBufferStride );
|
||
|
|
StaticMeshLOD.DepthOnlyIndexBuffer.SetIndices( TArray< uint32 >(), IndexBufferStride );
|
||
|
|
StaticMeshLOD.ReversedDepthOnlyIndexBuffer.SetIndices( TArray< uint32 >(), IndexBufferStride );
|
||
|
|
StaticMeshLOD.WireframeIndexBuffer.SetIndices( TArray< uint32 >(), IndexBufferStride );
|
||
|
|
StaticMeshLOD.AdjacencyIndexBuffer.SetIndices( TArray< uint32 >(), IndexBufferStride );
|
||
|
|
|
||
|
|
StaticMeshLOD.bHasAdjacencyInfo = false;
|
||
|
|
StaticMeshLOD.bHasDepthOnlyIndices = false;
|
||
|
|
StaticMeshLOD.bHasReversedIndices = false;
|
||
|
|
StaticMeshLOD.bHasReversedDepthOnlyIndices = false;
|
||
|
|
StaticMeshLOD.DepthOnlyNumTriangles = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::Compact()
|
||
|
|
{
|
||
|
|
static FElementIDRemappings Remappings;
|
||
|
|
|
||
|
|
// Compact vertices sparse array, generating a lookup from old to new indices in NewVertexIndexLookup
|
||
|
|
CompactSparseArrayElements( Vertices, Remappings.NewVertexIndexLookup );
|
||
|
|
|
||
|
|
// Compact rendering vertices sparse array, generating a lookup from old to new indices in NewVertexIndexLookup
|
||
|
|
CompactSparseArrayElements( RenderingVertices, Remappings.NewRenderingVertexIndexLookup );
|
||
|
|
|
||
|
|
// Compact edges sparse array, generating a lookup from old to new indices in NewEdgeIndexLookup
|
||
|
|
CompactSparseArrayElements( Edges, Remappings.NewEdgeIndexLookup );
|
||
|
|
|
||
|
|
// Compact sections sparse array, generating a lookup from old to new indices in NewSectionIndexLookup
|
||
|
|
CompactSparseArrayElements( Sections, Remappings.NewSectionIndexLookup );
|
||
|
|
|
||
|
|
Remappings.PerPolygon.Empty( Sections.GetMaxIndex() );
|
||
|
|
for( TSparseArray< FEditableStaticMeshSection >::TIterator It( Sections ); It; ++It )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = *It;
|
||
|
|
const int32 Index = It.GetIndex();
|
||
|
|
|
||
|
|
Remappings.PerPolygon.Insert( Index, FElementIDRemappings::FPerPolygonLookups() );
|
||
|
|
|
||
|
|
// Compact the polygon sparse array in each section
|
||
|
|
CompactSparseArrayElements( Section.Polygons, Remappings.PerPolygon[ Index ].NewPolygonIndexLookup );
|
||
|
|
|
||
|
|
// Compact the triangle sparse array in each section
|
||
|
|
CompactSparseArrayElements( Section.Triangles, Remappings.PerPolygon[ Index ].NewTriangleIndexLookup );
|
||
|
|
|
||
|
|
Section.MaxTriangles = Section.Triangles.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
FixUpElementIDs( Remappings );
|
||
|
|
RebuildRenderMesh();
|
||
|
|
|
||
|
|
// Prepare the inverse transaction to reverse the compaction
|
||
|
|
|
||
|
|
FUncompactChangeInput UncompactChangeInput;
|
||
|
|
InvertRemapTable( UncompactChangeInput.ElementIDRemappings.NewVertexIndexLookup, Remappings.NewVertexIndexLookup );
|
||
|
|
InvertRemapTable( UncompactChangeInput.ElementIDRemappings.NewRenderingVertexIndexLookup, Remappings.NewRenderingVertexIndexLookup );
|
||
|
|
InvertRemapTable( UncompactChangeInput.ElementIDRemappings.NewEdgeIndexLookup, Remappings.NewEdgeIndexLookup );
|
||
|
|
InvertRemapTable( UncompactChangeInput.ElementIDRemappings.NewSectionIndexLookup, Remappings.NewSectionIndexLookup );
|
||
|
|
|
||
|
|
for( TSparseArray< FEditableStaticMeshSection >::TIterator It( Sections ); It; ++It )
|
||
|
|
{
|
||
|
|
const int32 Index = It.GetIndex();
|
||
|
|
const int32 RemappedIndex = UncompactChangeInput.ElementIDRemappings.NewSectionIndexLookup[ Index ].GetValue();
|
||
|
|
|
||
|
|
UncompactChangeInput.ElementIDRemappings.PerPolygon.Insert( RemappedIndex, FElementIDRemappings::FPerPolygonLookups() );
|
||
|
|
auto& PerPolygon = UncompactChangeInput.ElementIDRemappings.PerPolygon[ RemappedIndex ];
|
||
|
|
InvertRemapTable( PerPolygon.NewPolygonIndexLookup, Remappings.PerPolygon[ Index ].NewPolygonIndexLookup );
|
||
|
|
InvertRemapTable( PerPolygon.NewTriangleIndexLookup, Remappings.PerPolygon[ Index ].NewTriangleIndexLookup );
|
||
|
|
}
|
||
|
|
|
||
|
|
AddUndo( MakeUnique<FUncompactChange>( MoveTemp( UncompactChangeInput ) ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::Uncompact( const FElementIDRemappings& Remappings )
|
||
|
|
{
|
||
|
|
// Uncompact vertices sparse array, remapping elements according to NewVertexIndexLookup
|
||
|
|
UncompactSparseArrayElements( Vertices, Remappings.NewVertexIndexLookup );
|
||
|
|
|
||
|
|
// Uncompact vertices sparse array, remapping elements according to NewVertexIndexLookup
|
||
|
|
UncompactSparseArrayElements( RenderingVertices, Remappings.NewRenderingVertexIndexLookup );
|
||
|
|
|
||
|
|
// Uncompact vertices sparse array, remapping elements according to NewVertexIndexLookup
|
||
|
|
UncompactSparseArrayElements( Edges, Remappings.NewEdgeIndexLookup );
|
||
|
|
|
||
|
|
// Uncompact vertices sparse array, remapping elements according to NewVertexIndexLookup
|
||
|
|
UncompactSparseArrayElements( Sections, Remappings.NewSectionIndexLookup );
|
||
|
|
|
||
|
|
for( TSparseArray< FEditableStaticMeshSection >::TIterator It( Sections ); It; ++It )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = *It;
|
||
|
|
const int32 Index = It.GetIndex();
|
||
|
|
check( Remappings.PerPolygon.IsAllocated( Index ) );
|
||
|
|
|
||
|
|
// Uncompact the polygon sparse array in each section
|
||
|
|
UncompactSparseArrayElements( Section.Polygons, Remappings.PerPolygon[ Index ].NewPolygonIndexLookup );
|
||
|
|
|
||
|
|
// Uncompact the triangle sparse array in each section
|
||
|
|
UncompactSparseArrayElements( Section.Triangles, Remappings.PerPolygon[ Index ].NewTriangleIndexLookup );
|
||
|
|
|
||
|
|
Section.MaxTriangles = Section.Triangles.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
FixUpElementIDs( Remappings );
|
||
|
|
RebuildRenderMesh();
|
||
|
|
|
||
|
|
AddUndo( MakeUnique<FCompactChange>() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::Serialize( FArchive& Ar )
|
||
|
|
{
|
||
|
|
Super::Serialize( Ar );
|
||
|
|
|
||
|
|
Ar.UsingCustomVersion( FEditableMeshCustomVersion::GUID );
|
||
|
|
|
||
|
|
if( Ar.CustomVer( FEditableMeshCustomVersion::GUID ) >= FEditableMeshCustomVersion::TextureCoordinateAndSubdivisionCounts )
|
||
|
|
{
|
||
|
|
Ar << TextureCoordinateCount;
|
||
|
|
Ar << SubdivisionCount;
|
||
|
|
}
|
||
|
|
else if( Ar.IsLoading() )
|
||
|
|
{
|
||
|
|
TextureCoordinateCount = ( StaticMesh != nullptr && StaticMesh->RenderData != nullptr ) ? StaticMesh->RenderData->LODResources[ 0 ].GetNumTexCoords() : 2;
|
||
|
|
SubdivisionCount = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
SerializeSparseArray( Ar, Vertices );
|
||
|
|
SerializeSparseArray( Ar, RenderingVertices ); // @todo mesheditor serialization: Should not need to be serialized if we triangulate after load
|
||
|
|
SerializeSparseArray( Ar, Edges );
|
||
|
|
SerializeSparseArray( Ar, Sections );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::StartModification( const EMeshModificationType MeshModificationType, const EMeshTopologyChange MeshTopologyChange )
|
||
|
|
{
|
||
|
|
if( ensure( !IsBeingModified() ) )
|
||
|
|
{
|
||
|
|
bIsBeingModified = true;
|
||
|
|
|
||
|
|
// Should be nothing in the undo stack if we're just starting to modify the mesh now
|
||
|
|
ensure( !this->AnyChangesToUndo() );
|
||
|
|
|
||
|
|
FStartOrEndModificationChangeInput RevertInput;
|
||
|
|
RevertInput.bStartModification = false;
|
||
|
|
RevertInput.MeshModificationType = MeshModificationType;
|
||
|
|
RevertInput.MeshTopologyChange = MeshTopologyChange;
|
||
|
|
AddUndo( MakeUnique<FStartOrEndModificationChange>( MoveTemp( RevertInput ) ) );
|
||
|
|
|
||
|
|
this->CurrentModificationType = MeshModificationType;
|
||
|
|
this->CurrentToplogyChange = MeshTopologyChange;
|
||
|
|
|
||
|
|
// @todo mesheditor debug: Disable noisy mesh editor spew by default (here and elsewhere)
|
||
|
|
// UE_LOG( LogMeshEditingRuntime, Log, TEXT( "UEditableStaticMesh::StartModification START: %s" ), *SubMeshAddress.ToString() );
|
||
|
|
FAutoScopedDurationTimer FunctionTimer;
|
||
|
|
|
||
|
|
// @todo mesheditor undo: We're not using traditional transactions to undo mesh changes yet, but we still want to dirty the mesh package
|
||
|
|
// Also, should we even need the Initializing type? Should we not wait for the first modification before dirtying the package?
|
||
|
|
if( 0 )
|
||
|
|
{
|
||
|
|
this->SetFlags( RF_Transactional );
|
||
|
|
this->Modify();
|
||
|
|
|
||
|
|
StaticMesh->SetFlags( RF_Transactional );
|
||
|
|
StaticMesh->Modify();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
StaticMesh->MarkPackageDirty();
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool bRefreshBounds = CurrentModificationType == EMeshModificationType::Final; // @todo mesheditor perf: Only do this if we may have changed the bounds
|
||
|
|
const bool bInvalidateLighting = ( CurrentModificationType == EMeshModificationType::FirstInterim || CurrentModificationType == EMeshModificationType::Final ); // @todo mesheditor perf: We can avoid invalidating lighting on 'Final' if we know that a 'FirstInterim' happened since the last 'Final'
|
||
|
|
RebuildRenderMeshStart( bRefreshBounds, bInvalidateLighting );
|
||
|
|
|
||
|
|
// @todo mesheditor debug
|
||
|
|
// UE_LOG( LogMeshEditingRuntime, Log, TEXT( "UEditableStaticMesh::StartModification COMPLETE in %0.4fs" ), FunctionTimer.GetTime() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RebuildRenderMeshStart( const bool bRefreshBounds, const bool bInvalidateLighting )
|
||
|
|
{
|
||
|
|
// We're changing the mesh itself, so ALL static mesh components in the scene will need
|
||
|
|
// to be unregistered for this (and reregistered afterwards.)
|
||
|
|
RecreateRenderStateContext = MakeShareable( new FStaticMeshComponentRecreateRenderStateContext( StaticMesh, bInvalidateLighting, bRefreshBounds ) );
|
||
|
|
|
||
|
|
// Release the static mesh's resources.
|
||
|
|
StaticMesh->ReleaseResources();
|
||
|
|
|
||
|
|
// Flush the resource release commands to the rendering thread to ensure that the build doesn't occur while a resource is still
|
||
|
|
// allocated, and potentially accessing the UStaticMesh.
|
||
|
|
StaticMesh->ReleaseResourcesFence.Wait();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::EndModification( const bool bFromUndo )
|
||
|
|
{
|
||
|
|
if( ensure( IsBeingModified() ) )
|
||
|
|
{
|
||
|
|
// @todo mesheditor debug
|
||
|
|
// UE_LOG( LogMeshEditingRuntime, Log, TEXT( "UEditableStaticMesh::EndModification START (ModType=%i): %s" ), (int32)MeshModificationType, *SubMeshAddress.ToString() );
|
||
|
|
// FAutoScopedDurationTimer FunctionTimer;
|
||
|
|
|
||
|
|
if( CurrentModificationType == EMeshModificationType::Final || !bFromUndo )
|
||
|
|
{
|
||
|
|
// Update subdivision limit surface
|
||
|
|
if( CurrentToplogyChange == EMeshTopologyChange::TopologyChange )
|
||
|
|
{
|
||
|
|
// Mesh topology (or subdivision level or smoothing) may have changed, so go ahead and refresh our OpenSubdiv representation entirely
|
||
|
|
RefreshOpenSubdiv();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// No topology change, so we can ask OpenSubdiv to quickly generate new limit surface geometry
|
||
|
|
GenerateOpenSubdivLimitSurfaceData();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Every so often, compact the data.
|
||
|
|
// Note we only want to do this when actions are performed, not when they are being undone/redone
|
||
|
|
bool bDidCompact = false;
|
||
|
|
// @todo mesheditor: reinstate this block once we have refactored to 'uber mesh', so we can implement a callback to notify
|
||
|
|
// tools that IDs have changed.
|
||
|
|
// @todo mesheditor: rendering vertices need to have their indices preserved in FChanges. Suggest refactor to "VertexInstances",
|
||
|
|
// one per unique occurrence of a vertex with equal attributes.
|
||
|
|
if( false )
|
||
|
|
{
|
||
|
|
if( CurrentModificationType == EMeshModificationType::Final &&
|
||
|
|
CurrentToplogyChange == EMeshTopologyChange::TopologyChange &&
|
||
|
|
!bFromUndo )
|
||
|
|
{
|
||
|
|
if( ++PendingCompactCounter == CompactFrequency )
|
||
|
|
{
|
||
|
|
PendingCompactCounter = 0;
|
||
|
|
Compact();
|
||
|
|
bDidCompact = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If subdivision preview mode is active, we'll need to refresh the entire static mesh with data from OpenSubdiv
|
||
|
|
// @todo mesheditor subdiv perf: Ideally we can avoid refreshing the entire thing if only positions have changed, as per above
|
||
|
|
if( IsPreviewingSubdivisions() && ( CurrentModificationType == EMeshModificationType::Final || !bFromUndo ) )
|
||
|
|
{
|
||
|
|
if( !bDidCompact ) // If we did a Compact() in this function, the mesh will have already been rebuilt
|
||
|
|
{
|
||
|
|
RebuildRenderMeshInternal();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
RebuildRenderMeshFinish( CurrentModificationType == EMeshModificationType::Final );
|
||
|
|
|
||
|
|
// @todo mesheditor: Not currently sure if we need to do this or not. Also, we are trying to support runtime editing (no PostEditChange stuff)
|
||
|
|
// if( GIsEditor && MeshModificationType == EMeshModificationType::Final )
|
||
|
|
// {
|
||
|
|
// StaticMesh->PostEditChange();
|
||
|
|
// }
|
||
|
|
|
||
|
|
// @todo mesheditor debug
|
||
|
|
// UE_LOG( LogMeshEditingRuntime, Log, TEXT( "UEditableStaticMesh::EndModification COMPLETE in %0.4fs" ), FunctionTimer.GetTime() ); // @todo mesheditor: Shows bogus time values
|
||
|
|
|
||
|
|
FStartOrEndModificationChangeInput RevertInput;
|
||
|
|
RevertInput.bStartModification = true;
|
||
|
|
RevertInput.MeshModificationType = CurrentModificationType;
|
||
|
|
RevertInput.MeshTopologyChange = CurrentToplogyChange;
|
||
|
|
AddUndo( MakeUnique<FStartOrEndModificationChange>( MoveTemp( RevertInput ) ) );
|
||
|
|
|
||
|
|
bIsBeingModified = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RebuildRenderMeshFinish( const bool bUpdateCollision )
|
||
|
|
{
|
||
|
|
UpdateBoundsAndCollision( bUpdateCollision );
|
||
|
|
|
||
|
|
StaticMesh->InitResources();
|
||
|
|
|
||
|
|
// NOTE: This can call InvalidateLightingCache() on all components using this mesh, causing Modify() to be
|
||
|
|
// called on those components! Just something to be aware of when EndModification() is called within
|
||
|
|
// an undo transaction.
|
||
|
|
RecreateRenderStateContext.Reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsCommitted() const
|
||
|
|
{
|
||
|
|
return StaticMesh->EditableMesh == this;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsCommittedAsInstance() const
|
||
|
|
{
|
||
|
|
return StaticMesh != OriginalStaticMesh;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::Commit()
|
||
|
|
{
|
||
|
|
if( !IsCommitted() )
|
||
|
|
{
|
||
|
|
// Move the editable mesh to an inner of the static mesh, and set the static mesh's EditableMesh property.
|
||
|
|
Rename( nullptr, StaticMesh, REN_DontCreateRedirectors );
|
||
|
|
StaticMesh->EditableMesh = this;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
UEditableMesh* UEditableStaticMesh::CommitInstance( UPrimitiveComponent* ComponentToInstanceTo )
|
||
|
|
{
|
||
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>( ComponentToInstanceTo );
|
||
|
|
|
||
|
|
if( StaticMeshComponent )
|
||
|
|
{
|
||
|
|
// Duplicate the static mesh, putting it as an *inner* of the static mesh component.
|
||
|
|
// This is no longer a persistent asset, so clear the appropriate flags.
|
||
|
|
UStaticMesh* NewStaticMesh = DuplicateObject( OriginalStaticMesh, StaticMeshComponent );
|
||
|
|
NewStaticMesh->ClearFlags( RF_Public | RF_Standalone );
|
||
|
|
|
||
|
|
// Point the static mesh component to the new static mesh instance we just made for it
|
||
|
|
StaticMeshComponent->SetStaticMesh( NewStaticMesh );
|
||
|
|
|
||
|
|
// Duplicate this editable mesh to a new instance inside the new static mesh instance, and set the static mesh's EditableMesh property.
|
||
|
|
UEditableStaticMesh* NewEditableMesh = DuplicateObject( this, NewStaticMesh );
|
||
|
|
NewStaticMesh->EditableMesh = NewEditableMesh;
|
||
|
|
NewEditableMesh->StaticMesh = NewStaticMesh;
|
||
|
|
|
||
|
|
// Update the submesh address which will have changed now it's been instanced
|
||
|
|
NewEditableMesh->SetSubMeshAddress( UEditableMeshFactory::MakeSubmeshAddress( StaticMeshComponent, SubMeshAddress.LODIndex ) );
|
||
|
|
NewEditableMesh->RebuildRenderMesh();
|
||
|
|
|
||
|
|
return NewEditableMesh;
|
||
|
|
}
|
||
|
|
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::Revert()
|
||
|
|
{
|
||
|
|
// @todo
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
UEditableMesh* UEditableStaticMesh::RevertInstance()
|
||
|
|
{
|
||
|
|
// @todo
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::PropagateInstanceChanges()
|
||
|
|
{
|
||
|
|
if( IsCommittedAsInstance() )
|
||
|
|
{
|
||
|
|
// @todo mesheditor: we can only generate submesh addresses from a component. Since we don't have a component, we create a dummy one.
|
||
|
|
// Not really fond of this.
|
||
|
|
// Explore other possibilities, e.g. constructing a submesh address by hand (although the contents of MeshObjectPtr are supposed to be opaque)
|
||
|
|
UStaticMeshComponent* DummyComponent = NewObject<UStaticMeshComponent>();
|
||
|
|
DummyComponent->SetStaticMesh( OriginalStaticMesh );
|
||
|
|
|
||
|
|
UEditableStaticMesh* NewEditableMesh = DuplicateObject( this, OriginalStaticMesh );
|
||
|
|
OriginalStaticMesh->EditableMesh = NewEditableMesh;
|
||
|
|
NewEditableMesh->StaticMesh = OriginalStaticMesh;
|
||
|
|
NewEditableMesh->SetSubMeshAddress( UEditableMeshFactory::MakeSubmeshAddress( DummyComponent, SubMeshAddress.LODIndex ) );
|
||
|
|
NewEditableMesh->RebuildRenderMesh();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::UpdateBoundsAndCollision( const bool bUpdateCollision )
|
||
|
|
{
|
||
|
|
// @todo mesheditor: we will need to create a new DDC key once we are able to edit placed instances individually.
|
||
|
|
// Will need to find a way of deriving the key based on the mesh key and an instance number which remains constant,
|
||
|
|
// otherwise we risk filling the DDC with junk (i.e. using vertex positions etc is not scalable).
|
||
|
|
|
||
|
|
// Compute a new bounding box
|
||
|
|
// @todo mesheditor perf: Only do this if the bounds may have changed (need hinting)
|
||
|
|
{
|
||
|
|
FStaticMeshRenderData& StaticMeshRenderData = *StaticMesh->RenderData;
|
||
|
|
|
||
|
|
FBoxSphereBounds BoundingBoxAndSphere;
|
||
|
|
|
||
|
|
// @todo mesheditor LODs: Really we should store the bounds of LOD0 inside the static mesh. Our editable mesh might be for a different LOD.
|
||
|
|
|
||
|
|
// If we're in subdivision preview mode, use the bounds of the base cage mesh, so that simple collision
|
||
|
|
// queries will always include the base cage, even though the actual mesh geometry might be quite a bit smaller.
|
||
|
|
// This also relies on us specifically querying against the simple collision, which we do in a second pass after
|
||
|
|
// looking for meshes using a complex collision trace.
|
||
|
|
// @todo mesheditor: Ideally we are not storing an inflated bounds here just for base cage editor interaction
|
||
|
|
if( IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
BoundingBoxAndSphere = ComputeBoundingBoxAndSphere();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
FBox BoundingBox;
|
||
|
|
BoundingBox.Init();
|
||
|
|
|
||
|
|
// Could improve performance here if necessary:
|
||
|
|
// 1) cache polygon IDs per vertex (in order to quickly reject orphans) and just iterate vertex array; or
|
||
|
|
// 2) cache bounding box per polygon
|
||
|
|
// There are other cases where having polygon adjacency information (1) might be useful, so it's maybe worth considering.
|
||
|
|
|
||
|
|
for( const FEditableStaticMeshSection& Section : Sections )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygon& Polygon : Section.Polygons )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygonContourVertex& Vertex : Polygon.PerimeterContour.Vertices )
|
||
|
|
{
|
||
|
|
BoundingBox += Vertices[ Vertex.VertexID.GetValue() ].VertexPosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
BoundingBox.GetCenterAndExtents( /* Out */ BoundingBoxAndSphere.Origin, /* Out */ BoundingBoxAndSphere.BoxExtent );
|
||
|
|
|
||
|
|
// Calculate the bounding sphere, using the center of the bounding box as the origin.
|
||
|
|
BoundingBoxAndSphere.SphereRadius = 0.0f;
|
||
|
|
|
||
|
|
for( const FEditableStaticMeshSection& Section : Sections )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygon& Polygon : Section.Polygons )
|
||
|
|
{
|
||
|
|
for( const FEditableStaticMeshPolygonContourVertex& Vertex : Polygon.PerimeterContour.Vertices )
|
||
|
|
{
|
||
|
|
const FVector VertexPosition = Vertices[ Vertex.VertexID.GetValue() ].VertexPosition;
|
||
|
|
|
||
|
|
BoundingBoxAndSphere.SphereRadius = FMath::Max( ( VertexPosition - BoundingBoxAndSphere.Origin ).Size(), BoundingBoxAndSphere.SphereRadius );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
StaticMeshRenderData.Bounds = BoundingBoxAndSphere;
|
||
|
|
StaticMesh->CalculateExtendedBounds();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Refresh collision (only if the interaction has finished though -- this is really expensive!)
|
||
|
|
if( bUpdateCollision )
|
||
|
|
{
|
||
|
|
|
||
|
|
// @todo mesheditor collision: We're wiping the existing simplified collision and generating a simple bounding
|
||
|
|
// box collision, since that's the best we can do without impacting performance. We always using visibility (complex)
|
||
|
|
// collision for traces while mesh editing (for hover/selection), so simplified collision isn't really important.
|
||
|
|
const bool bRecreateSimplifiedCollision = true;
|
||
|
|
|
||
|
|
if( StaticMesh->BodySetup == nullptr )
|
||
|
|
{
|
||
|
|
StaticMesh->CreateBodySetup();
|
||
|
|
}
|
||
|
|
|
||
|
|
UBodySetup* BodySetup = StaticMesh->BodySetup;
|
||
|
|
|
||
|
|
// NOTE: We don't bother calling Modify() on the BodySetup as EndModification() will rebuild this guy after every undo
|
||
|
|
// BodySetup->Modify();
|
||
|
|
|
||
|
|
if( bRecreateSimplifiedCollision )
|
||
|
|
{
|
||
|
|
if( BodySetup->AggGeom.GetElementCount() > 0 )
|
||
|
|
{
|
||
|
|
BodySetup->RemoveSimpleCollision();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
BodySetup->InvalidatePhysicsData();
|
||
|
|
|
||
|
|
if( bRecreateSimplifiedCollision )
|
||
|
|
{
|
||
|
|
const FBoxSphereBounds Bounds = StaticMesh->GetBounds();
|
||
|
|
|
||
|
|
FKBoxElem BoxElem;
|
||
|
|
BoxElem.Center = Bounds.Origin;
|
||
|
|
BoxElem.X = Bounds.BoxExtent.X * 2.0f;
|
||
|
|
BoxElem.Y = Bounds.BoxExtent.Y * 2.0f;
|
||
|
|
BoxElem.Z = Bounds.BoxExtent.Z * 2.0f;
|
||
|
|
BodySetup->AggGeom.BoxElems.Add( BoxElem );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update all static mesh components that are using this mesh
|
||
|
|
// @todo mesheditor perf: This is a pretty heavy operation, and overlaps with what we're already doing in RecreateRenderStateContext
|
||
|
|
// a little bit. Ideally we do everything in a single pass. Furthermore, if this could be updated lazily it would be faster.
|
||
|
|
{
|
||
|
|
for( FObjectIterator Iter( UStaticMeshComponent::StaticClass() ); Iter; ++Iter )
|
||
|
|
{
|
||
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>( *Iter );
|
||
|
|
if( StaticMeshComponent->GetStaticMesh() == StaticMesh )
|
||
|
|
{
|
||
|
|
// it needs to recreate IF it already has been created
|
||
|
|
if( StaticMeshComponent->IsPhysicsStateCreated() )
|
||
|
|
{
|
||
|
|
StaticMeshComponent->RecreatePhysicsState();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetVertexCount() const
|
||
|
|
{
|
||
|
|
return Vertices.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetVertexArraySize() const
|
||
|
|
{
|
||
|
|
return Vertices.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsValidVertex( const FVertexID VertexID ) const
|
||
|
|
{
|
||
|
|
return VertexID.GetValue() >= 0 && VertexID.GetValue() < Vertices.GetMaxIndex() && Vertices.IsAllocated( VertexID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector4 UEditableStaticMesh::GetVertexAttribute( const FVertexID VertexID, const FName AttributeName, const int32 AttributeIndex ) const
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one position is supported
|
||
|
|
return FVector4( Vertex.VertexPosition, 0.0f );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexCornerSharpness() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one softness value is supported
|
||
|
|
return FVector4( Vertex.CornerSharpness, 0.0f, 0.0f, 0.0f );
|
||
|
|
}
|
||
|
|
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::GetVertexAttribute() called with unrecognized vertex attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
return FVector4( 0.0f );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetVertexAttribute_Internal( const FVertexID VertexID, const FName AttributeName, const int32 AttributeIndex, const FVector4& NewAttributeValue )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one position is supported
|
||
|
|
|
||
|
|
const FVector NewVertexPosition( NewAttributeValue );
|
||
|
|
|
||
|
|
Vertex.VertexPosition = NewVertexPosition;
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
// Set the position of all of the rendering vertices for this editable vertex
|
||
|
|
for( const FRenderingVertexID RenderingVertexID : Vertex.RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
check( RenderingVertices.IsAllocated( RenderingVertexID.GetValue() ) );
|
||
|
|
FVector& RenderingVertexPosition = StaticMeshLOD.PositionVertexBuffer.VertexPosition( RenderingVertexID.GetValue() );
|
||
|
|
RenderingVertexPosition = NewVertexPosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexCornerSharpness() )
|
||
|
|
{
|
||
|
|
// @todo mesheditor urgent: We need to use nice logged errors instead of checks() for many of the calls in this module, so that script users don't crash their editor while trying things out.
|
||
|
|
check( AttributeIndex == 0 ); // Only one softness value is supported
|
||
|
|
|
||
|
|
Vertex.CornerSharpness = NewAttributeValue.X;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::SetVertexAttribute() called with unrecognized vertex attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetVertexConnectedEdgeCount( const FVertexID VertexID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Vertices.IsAllocated( VertexID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
return Vertex.ConnectedEdgeIDs.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FEdgeID UEditableStaticMesh::GetVertexConnectedEdge( const FVertexID VertexID, const int32 ConnectedEdgeNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Vertices.IsAllocated( VertexID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
return Vertex.ConnectedEdgeIDs[ ConnectedEdgeNumber ];
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetRenderingVertexCount() const
|
||
|
|
{
|
||
|
|
return RenderingVertices.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetRenderingVertexArraySize() const
|
||
|
|
{
|
||
|
|
return RenderingVertices.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector4 UEditableStaticMesh::GetEdgeAttribute( const FEdgeID EdgeID, const FName AttributeName, const int32 AttributeIndex ) const
|
||
|
|
{
|
||
|
|
checkSlow( Edges.IsAllocated( EdgeID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::EdgeIsHard() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one edge is hard flag is supported
|
||
|
|
|
||
|
|
return FVector4( Edge.bIsHardEdge ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::EdgeCreaseSharpness() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one edge crease sharpness is supported
|
||
|
|
|
||
|
|
return FVector4( Edge.CreaseSharpness, 0.0f, 0.0f, 0.0f );
|
||
|
|
}
|
||
|
|
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::GetEdgeAttribute() called with unrecognized edge attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
return FVector4( 0.0f );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetEdgeAttribute_Internal( const FEdgeID EdgeID, const FName AttributeName, const int32 AttributeIndex, const FVector4& NewAttributeValue )
|
||
|
|
{
|
||
|
|
checkSlow( Edges.IsAllocated( EdgeID.GetValue() ) );
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::EdgeIsHard() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one edge is hard flag is supported
|
||
|
|
|
||
|
|
Edge.bIsHardEdge = !FMath::IsNearlyZero( NewAttributeValue.X );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::EdgeCreaseSharpness() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one edge crease sharpness is supported
|
||
|
|
|
||
|
|
Edge.CreaseSharpness = NewAttributeValue.X;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::SetEdgeAttribute() called with unrecognized edge attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetEdgeCount() const
|
||
|
|
{
|
||
|
|
return Edges.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetEdgeArraySize() const
|
||
|
|
{
|
||
|
|
return Edges.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsValidEdge( const FEdgeID EdgeID ) const
|
||
|
|
{
|
||
|
|
return EdgeID.GetValue() >= 0 && EdgeID.GetValue() < Edges.GetMaxIndex() && Edges.IsAllocated( EdgeID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVertexID UEditableStaticMesh::GetEdgeVertex( const FEdgeID EdgeID, const int32 EdgeVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( EdgeVertexNumber >= 0 && EdgeVertexNumber < 2 );
|
||
|
|
checkSlow( Edges.IsAllocated( EdgeID.GetValue() ) );
|
||
|
|
return Edges[ EdgeID.GetValue() ].VertexIDs[ EdgeVertexNumber ];
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetEdgeConnectedPolygonCount( const FEdgeID EdgeID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Edges.IsAllocated( EdgeID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
return Edge.ConnectedPolygons.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FPolygonRef UEditableStaticMesh::GetEdgeConnectedPolygon( const FEdgeID EdgeID, const int32 ConnectedPolygonNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Edges.IsAllocated( EdgeID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
const FPolygonRef& PolygonRef = Edge.ConnectedPolygons[ ConnectedPolygonNumber ];
|
||
|
|
return PolygonRef;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetSectionCount() const
|
||
|
|
{
|
||
|
|
return Sections.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetSectionArraySize() const
|
||
|
|
{
|
||
|
|
return Sections.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsValidSection( const FSectionID SectionID ) const
|
||
|
|
{
|
||
|
|
return
|
||
|
|
SectionID.GetValue() >= 0 &&
|
||
|
|
SectionID.GetValue() < Sections.Num() &&
|
||
|
|
Sections.IsAllocated( SectionID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonCount( const FSectionID SectionID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
return Section.Polygons.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonArraySize( const FSectionID SectionID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
return Section.Polygons.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::IsValidPolygon( const FPolygonRef PolygonRef ) const
|
||
|
|
{
|
||
|
|
return
|
||
|
|
IsValidSection( PolygonRef.SectionID ) &&
|
||
|
|
PolygonRef.PolygonID.GetValue() >= 0 &&
|
||
|
|
PolygonRef.PolygonID.GetValue() < Sections[ PolygonRef.SectionID.GetValue() ].Polygons.GetMaxIndex() &&
|
||
|
|
Sections[ PolygonRef.SectionID.GetValue() ].Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetTriangleCount( const FSectionID SectionID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
|
||
|
|
return Section.Triangles.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetTriangleArraySize( const FSectionID SectionID ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
|
||
|
|
return Section.Triangles.GetMaxIndex();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonPerimeterVertexCount( const FPolygonRef PolygonRef ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
return Polygon.PerimeterContour.Vertices.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVertexID UEditableStaticMesh::GetPolygonPerimeterVertex( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContourVertex& ContourVertex = Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ];
|
||
|
|
return ContourVertex.VertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FRenderingVertexID UEditableStaticMesh::GetPolygonPerimeterRenderingVertex( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContourVertex& ContourVertex = Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ];
|
||
|
|
return ContourVertex.RenderingVertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector4 UEditableStaticMesh::GetPolygonPerimeterVertexAttribute( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber, const FName AttributeName, const int32 AttributeIndex ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContourVertex& ContourVertex = Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ];
|
||
|
|
return GetPolygonContourVertexAttribute( ContourVertex, AttributeName, AttributeIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector4 UEditableStaticMesh::GetPolygonContourVertexAttribute( const FEditableStaticMeshPolygonContourVertex& PolygonContourVertex, const FName AttributeName, const int32 AttributeIndex ) const
|
||
|
|
{
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one position is supported
|
||
|
|
return GetVertexAttribute( PolygonContourVertex.VertexID, AttributeName, AttributeIndex );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexNormal() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one normal is supported
|
||
|
|
return FVector4( PolygonContourVertex.Normal, 0.0f );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexTangent() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one tangent is supported
|
||
|
|
return FVector4( PolygonContourVertex.Tangent, 0.0f );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexBinormalSign() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one basis determinant sign is supported
|
||
|
|
return FVector4( PolygonContourVertex.BinormalSign );
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexTextureCoordinate() )
|
||
|
|
{
|
||
|
|
const int32 TextureCoordinateIndex = AttributeIndex;
|
||
|
|
if( TextureCoordinateIndex < PolygonContourVertex.VertexUVs.Num() )
|
||
|
|
{
|
||
|
|
const FVector2D TextureCoordinate = PolygonContourVertex.VertexUVs[ TextureCoordinateIndex ];
|
||
|
|
return FVector4( TextureCoordinate, FVector2D::ZeroVector );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
return FVector4( 0.0f );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexColor() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 );
|
||
|
|
return FVector4( FLinearColor( PolygonContourVertex.Color ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::GetPolygonVertexAttribute() called with unrecognized vertex attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
return FVector4( 0.0f );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetPolygonPerimeterVertexAttribute_Internal( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber, const FName AttributeName, const int32 AttributeIndex, const FVector4& NewAttributeValue )
|
||
|
|
{
|
||
|
|
if( AttributeName != UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
MakeDiscreetPolygonPerimeterRenderingVertexIfNeeded( PolygonRef, PolygonVertexNumber );
|
||
|
|
}
|
||
|
|
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
FEditableStaticMeshPolygonContourVertex& ContourVertex = Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ];
|
||
|
|
SetPolygonContourVertexAttribute( ContourVertex, AttributeName, AttributeIndex, NewAttributeValue );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetPolygonContourVertexAttribute( FEditableStaticMeshPolygonContourVertex& PolygonContourVertex, const FName AttributeName, const int32 AttributeIndex, const FVector4& NewAttributeValue )
|
||
|
|
{
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
SetVertexAttribute_Internal( PolygonContourVertex.VertexID, AttributeName, AttributeIndex, NewAttributeValue );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
const uint32 RenderingVertexIndex = PolygonContourVertex.RenderingVertexID.GetValue();
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexNormal() ||
|
||
|
|
AttributeName == UEditableMeshAttribute::VertexTangent() ||
|
||
|
|
AttributeName == UEditableMeshAttribute::VertexBinormalSign() )
|
||
|
|
{
|
||
|
|
check( AttributeIndex == 0 ); // Only one normal is supported
|
||
|
|
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexNormal() )
|
||
|
|
{
|
||
|
|
const FVector NewNormal( NewAttributeValue );
|
||
|
|
PolygonContourVertex.Normal = NewNormal;
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexTangent() )
|
||
|
|
{
|
||
|
|
const FVector NewTangent( NewAttributeValue );
|
||
|
|
PolygonContourVertex.Tangent = NewTangent;
|
||
|
|
}
|
||
|
|
if( AttributeName == UEditableMeshAttribute::VertexBinormalSign() )
|
||
|
|
{
|
||
|
|
PolygonContourVertex.BinormalSign = NewAttributeValue.X;
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
// @todo mesheditor perf: SetVertexTangents() and VertexTangentX/Y() functions actually does a bit of work to compute the basis every time.
|
||
|
|
// Ideally we can get/set this stuff directly to improve performance. This became slower after high precision basis values were added.
|
||
|
|
// @todo mesheditor perf: this is even more pertinent now we already have the binormal sign!
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexTangents(
|
||
|
|
RenderingVertexIndex,
|
||
|
|
PolygonContourVertex.Tangent,
|
||
|
|
FVector::CrossProduct( PolygonContourVertex.Normal, PolygonContourVertex.Tangent ).GetSafeNormal() * PolygonContourVertex.BinormalSign,
|
||
|
|
PolygonContourVertex.Normal );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexTextureCoordinate() )
|
||
|
|
{
|
||
|
|
const FVector2D NewTextureCoordinate( NewAttributeValue.X, NewAttributeValue.Y );
|
||
|
|
const int32 TextureCoordinateIndex = AttributeIndex;
|
||
|
|
|
||
|
|
if( PolygonContourVertex.VertexUVs.Num() <= TextureCoordinateIndex )
|
||
|
|
{
|
||
|
|
PolygonContourVertex.VertexUVs.SetNum( TextureCoordinateIndex + 1 );
|
||
|
|
}
|
||
|
|
PolygonContourVertex.VertexUVs[ TextureCoordinateIndex ] = NewTextureCoordinate;
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
check( TextureCoordinateIndex < GetTextureCoordinateCount() );
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexUV( RenderingVertexIndex, TextureCoordinateIndex, NewTextureCoordinate );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if( AttributeName == UEditableMeshAttribute::VertexColor() )
|
||
|
|
{
|
||
|
|
const FLinearColor NewLinearColor( NewAttributeValue.X, NewAttributeValue.Y, NewAttributeValue.Z, NewAttributeValue.W );
|
||
|
|
const FColor NewColor = NewLinearColor.ToFColor( true );
|
||
|
|
|
||
|
|
PolygonContourVertex.Color = NewColor;
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
if( StaticMeshLOD.ColorVertexBuffer.GetNumVertices() != RenderingVertices.GetMaxIndex() )
|
||
|
|
{
|
||
|
|
if( NewLinearColor != FLinearColor::White )
|
||
|
|
{
|
||
|
|
// Until now, we haven't needed a vertex color buffer.
|
||
|
|
// Force one to be generated now that we have a non-white vertex in the mesh.
|
||
|
|
RebuildRenderMeshInternal();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.VertexColor( RenderingVertexIndex ) = NewColor;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
checkf( 0, TEXT( "UEditableStaticMesh::SetPolygonPerimeterVertexAttribute() called with unrecognized vertex attribute name: %s (index: %i)" ), *AttributeName.ToString(), AttributeIndex );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonHoleCount( const FPolygonRef PolygonRef ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
return Polygon.HoleContours.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonHoleVertexCount( const FPolygonRef PolygonRef, const int32 HoleNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContour& Contour = Polygon.HoleContours[ HoleNumber ];
|
||
|
|
return Contour.Vertices.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVertexID UEditableStaticMesh::GetPolygonHoleVertex( const FPolygonRef PolygonRef, const int32 HoleNumber, const int32 PolygonVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContour& Contour = Polygon.HoleContours[ HoleNumber ];
|
||
|
|
return Contour.Vertices[ PolygonVertexNumber ].VertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FRenderingVertexID UEditableStaticMesh::GetPolygonHoleRenderingVertex( const FPolygonRef PolygonRef, const int32 HoleNumber, const int32 PolygonVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContour& Contour = Polygon.HoleContours[ HoleNumber ];
|
||
|
|
return Contour.Vertices[ PolygonVertexNumber ].RenderingVertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector4 UEditableStaticMesh::GetPolygonHoleVertexAttribute( const FPolygonRef PolygonRef, const int32 HoleNumber, const int32 PolygonVertexNumber, const FName AttributeName, const int32 AttributeIndex ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshPolygonContour& Contour = Polygon.HoleContours[ HoleNumber ];
|
||
|
|
const FEditableStaticMeshPolygonContourVertex& ContourVertex = Contour.Vertices[ PolygonVertexNumber ];
|
||
|
|
return GetPolygonContourVertexAttribute( ContourVertex, AttributeName, AttributeIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetPolygonHoleVertexAttribute_Internal( const FPolygonRef PolygonRef, const int32 HoleNumber, const int32 PolygonVertexNumber, const FName AttributeName, const int32 AttributeIndex, const FVector4& NewAttributeValue )
|
||
|
|
{
|
||
|
|
if( AttributeName != UEditableMeshAttribute::VertexPosition() )
|
||
|
|
{
|
||
|
|
// @todo mesheditor hole: We need a version of this function for holes.
|
||
|
|
// MakeDiscreetPolygonPerimeterRenderingVertexIfNeeded( PolygonRef, PolygonVertexNumber );
|
||
|
|
}
|
||
|
|
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
FEditableStaticMeshPolygonContour& Contour = Polygon.HoleContours[ HoleNumber ];
|
||
|
|
FEditableStaticMeshPolygonContourVertex& ContourVertex = Contour.Vertices[ PolygonVertexNumber ];
|
||
|
|
SetPolygonContourVertexAttribute( ContourVertex, AttributeName, AttributeIndex, NewAttributeValue );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int32 UEditableStaticMesh::GetPolygonTriangulatedTriangleCount( const FPolygonRef PolygonRef ) const
|
||
|
|
{
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
return Polygon.TriangulatedPolygonTriangleIndices.Num();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FVector UEditableStaticMesh::GetPolygonTriangulatedTriangleVertexPosition( const FPolygonRef PolygonRef, const int32 PolygonTriangleNumber, const int32 TriangleVertexNumber ) const
|
||
|
|
{
|
||
|
|
checkSlow( TriangleVertexNumber >= 0 && TriangleVertexNumber < 3 );
|
||
|
|
checkSlow( Sections.IsAllocated( PolygonRef.SectionID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
checkSlow( Section.Polygons.IsAllocated( PolygonRef.PolygonID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FTriangleID TriangleID = Polygon.TriangulatedPolygonTriangleIndices[ PolygonTriangleNumber ];
|
||
|
|
checkSlow( Section.Triangles.IsAllocated( TriangleID.GetValue() ) );
|
||
|
|
const FEditableStaticMeshTriangle& Triangle = Section.Triangles[ TriangleID.GetValue() ];
|
||
|
|
|
||
|
|
const FRenderingVertexID RenderingVertexID = Triangle.RenderingVertexIDs[ TriangleVertexNumber ];
|
||
|
|
checkSlow( RenderingVertices.IsAllocated( RenderingVertexID.GetValue() ) );
|
||
|
|
const FVertexID VertexID = RenderingVertices[ RenderingVertexID.GetValue() ].VertexID;
|
||
|
|
|
||
|
|
checkSlow( Vertices.IsAllocated( VertexID.GetValue() ) );
|
||
|
|
return Vertices[ VertexID.GetValue() ].VertexPosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::CreateEmptyVertexRange_Internal( const int32 NumVerticesToAdd, const TArray<FVertexID>* OverrideVertexIDsForRedo, TArray<FVertexID>& OutNewVertexIDs )
|
||
|
|
{
|
||
|
|
check( NumVerticesToAdd > 0 );
|
||
|
|
|
||
|
|
OutNewVertexIDs.Reset();
|
||
|
|
|
||
|
|
static TArray<FEditableStaticMeshVertex> NewVertices;
|
||
|
|
NewVertices.Reset();
|
||
|
|
NewVertices.AddDefaulted( NumVerticesToAdd );
|
||
|
|
|
||
|
|
for( int32 VertexToAddNumber = 0; VertexToAddNumber < NumVerticesToAdd; ++VertexToAddNumber )
|
||
|
|
{
|
||
|
|
FVertexID NewVertexID;
|
||
|
|
if( OverrideVertexIDsForRedo != nullptr )
|
||
|
|
{
|
||
|
|
NewVertexID = ( *OverrideVertexIDsForRedo )[ VertexToAddNumber ];
|
||
|
|
Vertices.Insert( NewVertexID.GetValue(), FEditableStaticMeshVertex() );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
NewVertexID = FVertexID( Vertices.Add( FEditableStaticMeshVertex() ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
FEditableStaticMeshVertex& NewVertex = Vertices[ NewVertexID.GetValue() ];
|
||
|
|
|
||
|
|
// Default position
|
||
|
|
NewVertex.VertexPosition = FVector::ZeroVector;
|
||
|
|
|
||
|
|
// Default corner sharpness
|
||
|
|
NewVertex.CornerSharpness = 0.0f;
|
||
|
|
|
||
|
|
// NOTE: The vertex starts out with no rendering vertex indices. Those will be setup when the vertex is
|
||
|
|
// connected to a polygon
|
||
|
|
|
||
|
|
OutNewVertexIDs.Add( NewVertexID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::CreateEdge_Internal( const FVertexID VertexIDA, const FVertexID VertexIDB, const TArray<FPolygonRef>& ConnectedPolygons, const FEdgeID OverrideEdgeIDForRedo, FEdgeID& OutNewEdgeID )
|
||
|
|
{
|
||
|
|
FEdgeID NewEdgeID;
|
||
|
|
|
||
|
|
if( OverrideEdgeIDForRedo != FEdgeID::Invalid )
|
||
|
|
{
|
||
|
|
NewEdgeID = OverrideEdgeIDForRedo;
|
||
|
|
Edges.Insert( NewEdgeID.GetValue(), FEditableStaticMeshEdge() );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
NewEdgeID = FEdgeID( Edges.Add( FEditableStaticMeshEdge() ) );
|
||
|
|
}
|
||
|
|
FEditableStaticMeshEdge& NewEdge = Edges[ NewEdgeID.GetValue() ];
|
||
|
|
|
||
|
|
NewEdge.VertexIDs[ 0 ] = VertexIDA;
|
||
|
|
NewEdge.VertexIDs[ 1 ] = VertexIDB;
|
||
|
|
|
||
|
|
NewEdge.ConnectedPolygons = ConnectedPolygons;
|
||
|
|
|
||
|
|
NewEdge.bIsHardEdge = false;
|
||
|
|
NewEdge.CreaseSharpness = 0.0f;
|
||
|
|
|
||
|
|
// Connect the edge to its vertices
|
||
|
|
Vertices[ NewEdge.VertexIDs[ 0 ].GetValue() ].ConnectedEdgeIDs.Add( NewEdgeID );
|
||
|
|
Vertices[ NewEdge.VertexIDs[ 1 ].GetValue() ].ConnectedEdgeIDs.Add( NewEdgeID );
|
||
|
|
|
||
|
|
OutNewEdgeID = NewEdgeID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::CreatePolygon_Internal( const FSectionID SectionID, const TArray<FVertexID>& VertexIDs, const TArray<TArray<FVertexID>>& VertexIDsForEachHole, const FPolygonID OverridePolygonIDForRedo, FPolygonRef& OutNewPolygonRef, TArray<FEdgeID>& OutNewEdgeIDs )
|
||
|
|
{
|
||
|
|
OutNewEdgeIDs.Reset();
|
||
|
|
|
||
|
|
// All polygons must have at least three vertices
|
||
|
|
check( VertexIDs.Num() >= 3 );
|
||
|
|
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
|
||
|
|
FPolygonID NewPolygonID;
|
||
|
|
if( OverridePolygonIDForRedo != FPolygonID::Invalid )
|
||
|
|
{
|
||
|
|
NewPolygonID = OverridePolygonIDForRedo;
|
||
|
|
Section.Polygons.Insert( NewPolygonID.GetValue(), FEditableStaticMeshPolygon() );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
NewPolygonID = FPolygonID( Section.Polygons.Add( FEditableStaticMeshPolygon() ) );
|
||
|
|
}
|
||
|
|
FEditableStaticMeshPolygon& NewPolygon = Section.Polygons[ NewPolygonID.GetValue() ];
|
||
|
|
|
||
|
|
const FPolygonRef PolygonRef( SectionID, NewPolygonID );
|
||
|
|
|
||
|
|
|
||
|
|
// Set our vertex IDs, then tell all of our edges that we are now connected to them
|
||
|
|
{
|
||
|
|
{
|
||
|
|
// Create new rendering vertices for the polygon. One for each vertex ID.
|
||
|
|
NewPolygon.PerimeterContour.Vertices.SetNum( VertexIDs.Num(), false );
|
||
|
|
for( int32 PerimeterVertexNumber = 0; PerimeterVertexNumber < VertexIDs.Num(); ++PerimeterVertexNumber )
|
||
|
|
{
|
||
|
|
const FVertexID VertexID = VertexIDs[ PerimeterVertexNumber ];
|
||
|
|
NewPolygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].VertexID = VertexID;
|
||
|
|
const FRenderingVertexID NewRenderingVertexID = AddNewRenderingVertexToPolygonPerimeter( PolygonRef, PerimeterVertexNumber );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure we have valid edges that connect the incoming vertex IDs. We'll create any edges that are missing
|
||
|
|
static TArray<FEdgeID> NewEdgeIDsForPolygonPerimeter;
|
||
|
|
NewEdgeIDsForPolygonPerimeter.Reset();
|
||
|
|
CreateMissingPolygonPerimeterEdges( PolygonRef, /* Out */ NewEdgeIDsForPolygonPerimeter );
|
||
|
|
|
||
|
|
OutNewEdgeIDs.Append( NewEdgeIDsForPolygonPerimeter );
|
||
|
|
|
||
|
|
static TArray< FEdgeID > ContourEdgeIDs;
|
||
|
|
GetPolygonPerimeterEdges( PolygonRef, /* Out */ ContourEdgeIDs );
|
||
|
|
for( const FEdgeID EdgeID : ContourEdgeIDs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
FPolygonRef& NewPolygonRef = *new( Edge.ConnectedPolygons ) FPolygonRef( PolygonRef );
|
||
|
|
}
|
||
|
|
|
||
|
|
const int32 NumPolygonHoles = VertexIDsForEachHole.Num();
|
||
|
|
for( int32 HoleNumber = 0; HoleNumber < NumPolygonHoles; ++HoleNumber )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshPolygonContour& HoleContour = *new( NewPolygon.HoleContours ) FEditableStaticMeshPolygonContour();
|
||
|
|
|
||
|
|
{
|
||
|
|
// Create new rendering vertices for the polygon hole. One for each vertex ID.
|
||
|
|
HoleContour.Vertices.SetNum( VertexIDsForEachHole[ HoleNumber ].Num(), false );
|
||
|
|
for( int32 HoleVertexNumber = 0; HoleVertexNumber < HoleContour.Vertices.Num(); ++HoleVertexNumber )
|
||
|
|
{
|
||
|
|
// @todo mesheditor: support for holes
|
||
|
|
const FVertexID VertexID = HoleContour.Vertices[ HoleVertexNumber ].VertexID;
|
||
|
|
HoleContour.Vertices[ HoleVertexNumber ].VertexID = VertexID;
|
||
|
|
|
||
|
|
// @todo mesheditor holes
|
||
|
|
// const FRenderingVertexID NewRenderingVertexID = AddNewRenderingVertexToPolygonHole( PolygonRef, HoleIndex, HoleVertexNumber );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure we have valid edges that connect the incoming hole vertex IDs. We'll create any edges that are missing
|
||
|
|
static TArray<FEdgeID> NewEdgeIDsForPolygonHole;
|
||
|
|
NewEdgeIDsForPolygonHole.Reset();
|
||
|
|
CreateMissingPolygonHoleEdges( PolygonRef, HoleNumber, /* Out */ NewEdgeIDsForPolygonHole );
|
||
|
|
|
||
|
|
OutNewEdgeIDs.Append( NewEdgeIDsForPolygonHole );
|
||
|
|
|
||
|
|
GetPolygonHoleEdges( PolygonRef, HoleNumber, /* Out */ ContourEdgeIDs );
|
||
|
|
for( const FEdgeID EdgeID : ContourEdgeIDs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
FPolygonRef& NewPolygonRef = *new( Edge.ConnectedPolygons ) FPolygonRef( PolygonRef );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Generate triangles for the new polygon
|
||
|
|
{
|
||
|
|
static TArray<FPolygonRef> PolygonsToRetriangulate;
|
||
|
|
PolygonsToRetriangulate.Reset();
|
||
|
|
PolygonsToRetriangulate.Add( PolygonRef );
|
||
|
|
const bool bOnlyOnUndo = false;
|
||
|
|
|
||
|
|
// NOTE: We don't keep the revert step for retriangulation, because this DeletePolygons_Internal() is used to clean up
|
||
|
|
// newly-created polygons, so we'll never need to do our own rollback.
|
||
|
|
const bool bWasUndoEnabled = bAllowUndo;
|
||
|
|
SetAllowUndo( false );
|
||
|
|
|
||
|
|
RetriangulatePolygons( PolygonsToRetriangulate, bOnlyOnUndo );
|
||
|
|
|
||
|
|
SetAllowUndo( bWasUndoEnabled );
|
||
|
|
}
|
||
|
|
|
||
|
|
OutNewPolygonRef = PolygonRef;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RetriangulatePolygons( const TArray<FPolygonRef>& PolygonRefs, const bool bOnlyOnUndo )
|
||
|
|
{
|
||
|
|
FRetrianglulatePolygonsChangeInput RevertInput;
|
||
|
|
RevertInput.PolygonRefs = PolygonRefs;
|
||
|
|
RevertInput.bOnlyOnUndo = !bOnlyOnUndo;
|
||
|
|
|
||
|
|
if( !bOnlyOnUndo )
|
||
|
|
{
|
||
|
|
for( const FPolygonRef PolygonRef : PolygonRefs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
// @todo mesheditor urgent: Any time a polygon's shape changes (vertices/edges are moved, new vertices added, etc), we should
|
||
|
|
// wipe the polygon's triangles and call MakeTrianglesForPolygon() again to update the polygon's triangles. This is critical to
|
||
|
|
// handle cases where the polygon becomes or is no longer degenerate, or convex -> concave transitions and vice versa
|
||
|
|
static TArray<int32> PerimeterVertexNumbersForTriangles;
|
||
|
|
ComputePolygonTriangulation( PolygonRef, /* Out */ PerimeterVertexNumbersForTriangles );
|
||
|
|
check( PerimeterVertexNumbersForTriangles.Num() > 0 );
|
||
|
|
|
||
|
|
// @todo mesheditor holes: This code will need some work to support vertex IDs that come from hole contours
|
||
|
|
static TArray<FRenderingVertexID> TrianglesRenderingVertexIDs;
|
||
|
|
TrianglesRenderingVertexIDs.SetNum( PerimeterVertexNumbersForTriangles.Num(), false );
|
||
|
|
for( int32 TriangleVerticesNumber = 0; TriangleVerticesNumber < PerimeterVertexNumbersForTriangles.Num(); ++TriangleVerticesNumber )
|
||
|
|
{
|
||
|
|
const int32 PerimeterVertexNumber = PerimeterVertexNumbersForTriangles[ TriangleVerticesNumber ];
|
||
|
|
TrianglesRenderingVertexIDs[ TriangleVerticesNumber ] = Polygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].RenderingVertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check to see whether the index buffer needs to be updated
|
||
|
|
bool bNeedsUpdatedTriangles = false;
|
||
|
|
{
|
||
|
|
if( Polygon.TriangulatedPolygonTriangleIndices.Num() * 3 != TrianglesRenderingVertexIDs.Num() )
|
||
|
|
{
|
||
|
|
// Triangle count has changed, so we definitely need new triangles!
|
||
|
|
bNeedsUpdatedTriangles = true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// @todo mesheditor: Untested code path
|
||
|
|
int32 NextNewTriangleVertexNumber = 0;
|
||
|
|
for( int32 TriangleIter = 0; !bNeedsUpdatedTriangles && TriangleIter < Polygon.TriangulatedPolygonTriangleIndices.Num(); ++TriangleIter )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshTriangle& OldTriangle = Section.Triangles[ Polygon.TriangulatedPolygonTriangleIndices[ TriangleIter ].GetValue() ];
|
||
|
|
for( int32 TriangleVertexIter = 0; TriangleVertexIter < 3; ++TriangleVertexIter )
|
||
|
|
{
|
||
|
|
if( OldTriangle.RenderingVertexIDs[ TriangleVertexIter ] != TrianglesRenderingVertexIDs[ NextNewTriangleVertexNumber ] )
|
||
|
|
{
|
||
|
|
bNeedsUpdatedTriangles = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
++NextNewTriangleVertexNumber;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Has anything changed?
|
||
|
|
if( bNeedsUpdatedTriangles )
|
||
|
|
{
|
||
|
|
const uint32 RenderingSectionIndex = Section.RenderingSectionIndex;
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
FStaticMeshSection& RenderingSection = StaticMeshLOD.Sections[ RenderingSectionIndex ];
|
||
|
|
|
||
|
|
// Remove the old triangles
|
||
|
|
DeletePolygonTriangles( PolygonRef );
|
||
|
|
|
||
|
|
// Add new triangles
|
||
|
|
{
|
||
|
|
// This is the number of triangles we are about to add
|
||
|
|
const int32 NumNewTriangles = TrianglesRenderingVertexIDs.Num() / 3;
|
||
|
|
|
||
|
|
// This is the number of entries currently unused in the Triangles sparse array
|
||
|
|
const int32 NumFreeTriangles = Section.Triangles.GetMaxIndex() - Section.Triangles.Num();
|
||
|
|
|
||
|
|
// This is the number of triangles we need to make extra space for (in both the sparse array and the index buffer)
|
||
|
|
const int32 NumTrianglesToReserve = FMath::Max( 0, NumNewTriangles - NumFreeTriangles );
|
||
|
|
|
||
|
|
// This is the number of triangles we will need to have allocated in the index buffer after adding the new triangles
|
||
|
|
const int32 NewTotalTriangles = Section.Triangles.GetMaxIndex() + NumTrianglesToReserve;
|
||
|
|
|
||
|
|
// Reserve extra triangles if necessary.
|
||
|
|
if( NumTrianglesToReserve > 0 )
|
||
|
|
{
|
||
|
|
Section.Triangles.Reserve( NewTotalTriangles );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Keep track of new min/max vertex indices
|
||
|
|
int32 MinVertexIndex = RenderingSection.MinVertexIndex;
|
||
|
|
int32 MaxVertexIndex = RenderingSection.MaxVertexIndex;
|
||
|
|
|
||
|
|
// Create empty triangles for all of the new triangles we need, and keep track of their triangle indices
|
||
|
|
static TArray<int32> NewTriangleIndices;
|
||
|
|
{
|
||
|
|
NewTriangleIndices.SetNumUninitialized( NumNewTriangles, false );
|
||
|
|
|
||
|
|
for( int32 TriangleToAddNumber = 0; TriangleToAddNumber < NumNewTriangles; ++TriangleToAddNumber )
|
||
|
|
{
|
||
|
|
const int32 NewTriangleIndex = Section.Triangles.Add( FEditableStaticMeshTriangle() );
|
||
|
|
NewTriangleIndices[ TriangleToAddNumber ] = NewTriangleIndex;
|
||
|
|
|
||
|
|
FEditableStaticMeshTriangle& NewTriangle = Section.Triangles[ NewTriangleIndex ];
|
||
|
|
for( int32 TriangleVertexNumber = 0; TriangleVertexNumber < 3; ++TriangleVertexNumber )
|
||
|
|
{
|
||
|
|
FRenderingVertexID RenderingVertexID = TrianglesRenderingVertexIDs[ TriangleToAddNumber * 3 + TriangleVertexNumber ];
|
||
|
|
NewTriangle.RenderingVertexIDs[ TriangleVertexNumber ] = RenderingVertexID;
|
||
|
|
MinVertexIndex = FMath::Min( MinVertexIndex, RenderingVertexID.GetValue() );
|
||
|
|
MaxVertexIndex = FMath::Max( MaxVertexIndex, RenderingVertexID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
Polygon.TriangulatedPolygonTriangleIndices.Add( FTriangleID( NewTriangleIndex ) );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update the index buffer
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
UpdateIndexBufferFormatIfNeeded( TrianglesRenderingVertexIDs );
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we need more space in the index buffer for this section, allocate it here
|
||
|
|
if( NewTotalTriangles > Section.MaxTriangles )
|
||
|
|
{
|
||
|
|
AllocateExtraIndicesForSection( PolygonRef.SectionID, NewTotalTriangles + IndexBufferInterSectionGap - Section.MaxTriangles );
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
for( int32 TriangleToAddNumber = 0; TriangleToAddNumber < NumNewTriangles; ++TriangleToAddNumber )
|
||
|
|
{
|
||
|
|
const int32 NewTriangleIndex = NewTriangleIndices[ TriangleToAddNumber ];
|
||
|
|
|
||
|
|
for( int32 TriangleVertexNumber = 0; TriangleVertexNumber < 3; ++TriangleVertexNumber )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.IndexBuffer.SetIndex(
|
||
|
|
FEditableStaticMeshSection::TriangleIndexToRenderingTriangleFirstIndex( RenderingSection, FTriangleID( NewTriangleIndex ) ) + TriangleVertexNumber,
|
||
|
|
TrianglesRenderingVertexIDs[ TriangleToAddNumber * 3 + TriangleVertexNumber ].GetValue() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( NumTrianglesToReserve > 0 )
|
||
|
|
{
|
||
|
|
RenderingSection.NumTriangles += NumTrianglesToReserve;
|
||
|
|
}
|
||
|
|
RenderingSection.MinVertexIndex = MinVertexIndex;
|
||
|
|
RenderingSection.MaxVertexIndex = MaxVertexIndex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#if 0 // Debug code for checking that sections are correctly allocated
|
||
|
|
UE_LOG( LogTemp, Log, TEXT( "RetriangulatePolygons" ) );
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
int32 RenderingSectionIndex = 0;
|
||
|
|
for( const FStaticMeshSection& RenderingSection : StaticMeshLOD.Sections )
|
||
|
|
{
|
||
|
|
const FSectionID SectionID = GetSectionForRenderingSectionIndex( RenderingSectionIndex );
|
||
|
|
check( SectionID != FSectionID::Invalid );
|
||
|
|
const FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
check( Section.MaterialIndex == RenderingSection.MaterialIndex );
|
||
|
|
|
||
|
|
UE_LOG( LogTemp, Log, TEXT( " %s: Material = %s, FirstIndex = %d, Num tris = %d, Max tris = %d" ),
|
||
|
|
*SectionID.ToString(),
|
||
|
|
*StaticMesh->GetMaterial(Section.MaterialIndex)->GetName(),
|
||
|
|
RenderingSection.FirstIndex,
|
||
|
|
RenderingSection.NumTriangles,
|
||
|
|
Section.MaxTriangles );
|
||
|
|
|
||
|
|
++RenderingSectionIndex;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
AddUndo( MakeUnique<FRetrianglulatePolygonsChange>( MoveTemp( RevertInput ) ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::AllocateExtraIndicesForSection( const FSectionID SectionID, int32 NumExtraTriangles )
|
||
|
|
{
|
||
|
|
check( Sections.IsAllocated( SectionID.GetValue() ) );
|
||
|
|
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
|
||
|
|
// Get current number of triangles allocated for this section
|
||
|
|
const int32 MaxTriangles = Section.MaxTriangles;
|
||
|
|
Section.MaxTriangles += NumExtraTriangles;
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
uint32 RenderingSectionIndex = Section.RenderingSectionIndex;
|
||
|
|
uint32 FirstIndex = StaticMeshLOD.Sections[ RenderingSectionIndex ].FirstIndex;
|
||
|
|
|
||
|
|
// Make room in the index buffer for the extra triangles, and update the mesh section's maximum triangle count
|
||
|
|
StaticMeshLOD.IndexBuffer.InsertIndices( FirstIndex + MaxTriangles * 3, nullptr, NumExtraTriangles * 3 );
|
||
|
|
|
||
|
|
// Adjust first index for all subsequent render sections to account for the extra indices just inserted.
|
||
|
|
// It is guaranteed that index buffer indices are in the same order as the rendering sections.
|
||
|
|
const uint32 NumRenderingSections = StaticMeshLOD.Sections.Num();
|
||
|
|
while( ++RenderingSectionIndex < NumRenderingSections )
|
||
|
|
{
|
||
|
|
check( StaticMeshLOD.Sections[ RenderingSectionIndex ].FirstIndex >= FirstIndex );
|
||
|
|
StaticMeshLOD.Sections[ RenderingSectionIndex ].FirstIndex += NumExtraTriangles * 3;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::CreateRenderingVertices( const TArray<FVertexID>& VertexIDs, const TOptional<FRenderingVertexID> OptionalCopyFromRenderingVertexID, TArray<FRenderingVertexID>& OutNewRenderingVertexIDs )
|
||
|
|
{
|
||
|
|
const int32 NumVerticesToAdd = VertexIDs.Num();
|
||
|
|
|
||
|
|
int32 NumFreeRenderingVertexIDs = RenderingVertices.GetMaxIndex() - RenderingVertices.Num();
|
||
|
|
check( NumFreeRenderingVertexIDs >= 0 );
|
||
|
|
|
||
|
|
RenderingVertices.Reserve( RenderingVertices.Num() + NumVerticesToAdd );
|
||
|
|
|
||
|
|
OutNewRenderingVertexIDs.Reset();
|
||
|
|
OutNewRenderingVertexIDs.Reserve( NumVerticesToAdd );
|
||
|
|
for( int32 VertexToAddNumber = 0; VertexToAddNumber < NumVerticesToAdd; ++VertexToAddNumber )
|
||
|
|
{
|
||
|
|
const int32 NewRenderingVertexIndex = RenderingVertices.AddUninitialized().Index;
|
||
|
|
RenderingVertices[ NewRenderingVertexIndex ].VertexID = VertexIDs[ VertexToAddNumber ];
|
||
|
|
|
||
|
|
const FRenderingVertexID NewRenderingVertexID( NewRenderingVertexIndex );
|
||
|
|
OutNewRenderingVertexIDs.Add( NewRenderingVertexID );
|
||
|
|
|
||
|
|
// Update the vertex
|
||
|
|
FEditableStaticMeshVertex& ReferencedVertex = Vertices[ VertexIDs[ VertexToAddNumber ].GetValue() ];
|
||
|
|
checkSlow( !ReferencedVertex.RenderingVertexIDs.Contains( NewRenderingVertexID ) );
|
||
|
|
ReferencedVertex.RenderingVertexIDs.Add( NewRenderingVertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
const int32 NumUVs = GetTextureCoordinateCount();
|
||
|
|
const bool bHasColors = StaticMeshLOD.ColorVertexBuffer.GetNumVertices() > 0;
|
||
|
|
|
||
|
|
const int32 OldVertexBufferRenderingVertexCount = StaticMeshLOD.PositionVertexBuffer.GetNumVertices();
|
||
|
|
const int32 NumNewVertexBufferRenderingVertices = FMath::Max( 0, NumVerticesToAdd - NumFreeRenderingVertexIDs );
|
||
|
|
|
||
|
|
static TArray<FStaticMeshBuildVertex> RenderingVerticesToAppend;
|
||
|
|
RenderingVerticesToAppend.SetNumUninitialized( NumNewVertexBufferRenderingVertices, false );
|
||
|
|
|
||
|
|
for( int32 VertexToAddNumber = 0; VertexToAddNumber < NumVerticesToAdd; ++VertexToAddNumber )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshVertex& ReferencedVertex = Vertices[ VertexIDs[ VertexToAddNumber ].GetValue() ];
|
||
|
|
|
||
|
|
const FRenderingVertexID NewRenderingVertexID = OutNewRenderingVertexIDs[ VertexToAddNumber ];
|
||
|
|
const int32 NewRenderingVertexIndex = NewRenderingVertexID.GetValue();
|
||
|
|
|
||
|
|
if( NewRenderingVertexIndex < OldVertexBufferRenderingVertexCount )
|
||
|
|
{
|
||
|
|
if( OptionalCopyFromRenderingVertexID.IsSet() )
|
||
|
|
{
|
||
|
|
const uint32 CopyFromRenderingVertexIndex = OptionalCopyFromRenderingVertexID->GetValue();
|
||
|
|
|
||
|
|
// Copy from the specified vertex
|
||
|
|
StaticMeshLOD.PositionVertexBuffer.VertexPosition( NewRenderingVertexIndex ) = StaticMeshLOD.PositionVertexBuffer.VertexPosition( CopyFromRenderingVertexIndex );
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexTangents(
|
||
|
|
NewRenderingVertexIndex,
|
||
|
|
StaticMeshLOD.VertexBuffer.VertexTangentX( CopyFromRenderingVertexIndex ),
|
||
|
|
StaticMeshLOD.VertexBuffer.VertexTangentY( CopyFromRenderingVertexIndex ),
|
||
|
|
StaticMeshLOD.VertexBuffer.VertexTangentZ( CopyFromRenderingVertexIndex ) );
|
||
|
|
for( int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexUV( NewRenderingVertexIndex, UVIndex, StaticMeshLOD.VertexBuffer.GetVertexUV( CopyFromRenderingVertexIndex, UVIndex ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
if( bHasColors )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.VertexColor( NewRenderingVertexIndex ) = StaticMeshLOD.ColorVertexBuffer.VertexColor( CopyFromRenderingVertexIndex );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Initialize the new vertices to some defaults
|
||
|
|
StaticMeshLOD.PositionVertexBuffer.VertexPosition( NewRenderingVertexIndex ) = ReferencedVertex.VertexPosition;
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexTangents(
|
||
|
|
NewRenderingVertexIndex,
|
||
|
|
FVector::ZeroVector,
|
||
|
|
FVector::ZeroVector,
|
||
|
|
FVector::ZeroVector );
|
||
|
|
for( int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.VertexBuffer.SetVertexUV( NewRenderingVertexIndex, UVIndex, FVector2D::ZeroVector );
|
||
|
|
}
|
||
|
|
|
||
|
|
if( bHasColors )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.VertexColor( NewRenderingVertexIndex ) = FColor::White;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
const int32 AppendVertexNumber = NewRenderingVertexIndex - OldVertexBufferRenderingVertexCount;
|
||
|
|
check( AppendVertexNumber >= 0 && AppendVertexNumber < NumNewVertexBufferRenderingVertices );
|
||
|
|
FStaticMeshBuildVertex& RenderingVertexToAppend = RenderingVerticesToAppend[ AppendVertexNumber ];
|
||
|
|
|
||
|
|
if( OptionalCopyFromRenderingVertexID.IsSet() )
|
||
|
|
{
|
||
|
|
const uint32 CopyFromRenderingVertexIndex = OptionalCopyFromRenderingVertexID->GetValue();
|
||
|
|
|
||
|
|
// Copy from the specified vertex
|
||
|
|
RenderingVertexToAppend.Position = StaticMeshLOD.PositionVertexBuffer.VertexPosition( CopyFromRenderingVertexIndex );
|
||
|
|
RenderingVertexToAppend.TangentX = StaticMeshLOD.VertexBuffer.VertexTangentX( CopyFromRenderingVertexIndex );
|
||
|
|
RenderingVertexToAppend.TangentY = StaticMeshLOD.VertexBuffer.VertexTangentY( CopyFromRenderingVertexIndex );
|
||
|
|
RenderingVertexToAppend.TangentZ = StaticMeshLOD.VertexBuffer.VertexTangentZ( CopyFromRenderingVertexIndex );
|
||
|
|
for( int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex )
|
||
|
|
{
|
||
|
|
RenderingVertexToAppend.UVs[ UVIndex ] = StaticMeshLOD.VertexBuffer.GetVertexUV( CopyFromRenderingVertexIndex, UVIndex );
|
||
|
|
}
|
||
|
|
RenderingVertexToAppend.Color = bHasColors ? StaticMeshLOD.ColorVertexBuffer.VertexColor( CopyFromRenderingVertexIndex ) : FColor::White;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Initialize the new vertices to some defaults
|
||
|
|
RenderingVertexToAppend.Position = ReferencedVertex.VertexPosition;
|
||
|
|
RenderingVertexToAppend.TangentX = FPackedNormal::ZeroNormal;
|
||
|
|
RenderingVertexToAppend.TangentY = FPackedNormal::ZeroNormal;
|
||
|
|
RenderingVertexToAppend.TangentZ = FPackedNormal::ZeroNormal;
|
||
|
|
for( int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex )
|
||
|
|
{
|
||
|
|
RenderingVertexToAppend.UVs[ UVIndex ] = FVector2D::ZeroVector;
|
||
|
|
}
|
||
|
|
RenderingVertexToAppend.Color = FColor::White;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if( RenderingVerticesToAppend.Num() > 0 )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.VertexBuffer.AppendVertices( RenderingVerticesToAppend.GetData(), RenderingVerticesToAppend.Num() );
|
||
|
|
StaticMeshLOD.PositionVertexBuffer.AppendVertices( RenderingVerticesToAppend.GetData(), RenderingVerticesToAppend.Num() );
|
||
|
|
if( bHasColors )
|
||
|
|
{
|
||
|
|
StaticMeshLOD.ColorVertexBuffer.AppendVertices( RenderingVerticesToAppend.GetData(), RenderingVerticesToAppend.Num() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeleteOrphanRenderingVertices( const TArray<FRenderingVertexID>& RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
// Don't actually delete any vertices, but instead just mark them as unused.
|
||
|
|
for( const FRenderingVertexID RenderingVertexIDToDelete : RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
RenderingVertices.RemoveAt( RenderingVertexIDToDelete.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
// @todo mesheditor urgent perf: Add a 'compact' feature that actually deletes unused vertices
|
||
|
|
// This will invalidate all existing IDs though, so it needs to be handled pretty carefully. Take a look
|
||
|
|
// at the history of this function in revision control to see an implementation that did this.
|
||
|
|
// Probably also, we'd want to do this for all other sparse arrays in the mesh too!
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeleteOrphanVertices_Internal( const TArray<FVertexID>& VertexIDsToDelete )
|
||
|
|
{
|
||
|
|
static TArray<FRenderingVertexID> RenderingVertexIDsToDelete;
|
||
|
|
RenderingVertexIDsToDelete.Reset();
|
||
|
|
|
||
|
|
for( int32 VertexNumber = 0; VertexNumber < VertexIDsToDelete.Num(); ++VertexNumber )
|
||
|
|
{
|
||
|
|
const FVertexID VertexID = VertexIDsToDelete[ VertexNumber ];
|
||
|
|
|
||
|
|
const FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
|
||
|
|
for( const FRenderingVertexID RenderingVertexID : Vertex.RenderingVertexIDs )
|
||
|
|
{
|
||
|
|
check( !RenderingVertexIDsToDelete.Contains( RenderingVertexID ) ); // Two vertices should never be sharing the same rendering vertex index
|
||
|
|
check( RenderingVertices.IsAllocated( RenderingVertexID.GetValue() ) );
|
||
|
|
RenderingVertexIDsToDelete.AddUnique( RenderingVertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Vertex must be orphaned before it is deleted!
|
||
|
|
check( Vertex.ConnectedEdgeIDs.Num() == 0 );
|
||
|
|
|
||
|
|
// Delete the vertex
|
||
|
|
Vertices.RemoveAt( VertexID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Delete the rendering vertices from the static mesh's vertex buffers
|
||
|
|
if( RenderingVertexIDsToDelete.Num() > 0 )
|
||
|
|
{
|
||
|
|
DeleteOrphanRenderingVertices( RenderingVertexIDsToDelete );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeleteEdges_Internal( const TArray<FEdgeID>& EdgeIDsToDelete, const bool bDeleteOrphanedVertices )
|
||
|
|
{
|
||
|
|
// Keep track of any vertices we orphaned, so we can delete them after we unhook everything
|
||
|
|
static TArray< FVertexID > OrphanedVertexIDs;
|
||
|
|
OrphanedVertexIDs.Reset();
|
||
|
|
|
||
|
|
for( const FEdgeID EdgeID : EdgeIDsToDelete )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
for( const FVertexID EdgeVertexID : Edge.VertexIDs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshVertex& Vertex = Vertices[ EdgeVertexID.GetValue() ];
|
||
|
|
const int32 NumRemovedEdges = Vertex.ConnectedEdgeIDs.RemoveSingle( EdgeID );
|
||
|
|
check( NumRemovedEdges == 1 );
|
||
|
|
|
||
|
|
// If the vertex has no more edges connected, we'll keep track of that so we can delete the vertex later
|
||
|
|
if( Vertex.ConnectedEdgeIDs.Num() == 0 )
|
||
|
|
{
|
||
|
|
check( !OrphanedVertexIDs.Contains( EdgeVertexID ) ); // Orphaned vertex shouldn't have already been orphaned by an earlier deleted edge passed into this function
|
||
|
|
OrphanedVertexIDs.Add( EdgeVertexID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Delete the edge
|
||
|
|
Edges.RemoveAt( EdgeID.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// If we orphaned any vertices and we were asked to delete those, then we'll go ahead and do that now.
|
||
|
|
if( bDeleteOrphanedVertices && OrphanedVertexIDs.Num() > 0 )
|
||
|
|
{
|
||
|
|
DeleteOrphanVertices( OrphanedVertexIDs );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeletePolygon_Internal( const FPolygonRef PolygonRef, const bool bDeleteOrphanedEdges, const bool bDeleteOrphanedVertices, const bool bDeleteEmptySections )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
|
||
|
|
// Keep track of any edges we orphaned, so we can delete them after we unhook everything
|
||
|
|
static TArray< FEdgeID > OrphanedEdgeIDs;
|
||
|
|
OrphanedEdgeIDs.Reset(); // @todo mesheditor perf: Possibly use inline allocators/alloca instead of statics all over the place (threading)
|
||
|
|
|
||
|
|
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
// Update all of our edges. They no longer connect with us.
|
||
|
|
{
|
||
|
|
static TArray< FEdgeID > ContourEdgeIDs;
|
||
|
|
GetPolygonPerimeterEdges( PolygonRef, /* Out */ ContourEdgeIDs );
|
||
|
|
for( const FEdgeID EdgeID : ContourEdgeIDs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
int32 ExistingPolygonNumber;
|
||
|
|
bool bWasFound = Edge.ConnectedPolygons.Find( PolygonRef, /* Out */ ExistingPolygonNumber );
|
||
|
|
|
||
|
|
check( bWasFound );
|
||
|
|
Edge.ConnectedPolygons.RemoveAt( ExistingPolygonNumber );
|
||
|
|
|
||
|
|
// If the edge has no more polygons connected, we'll keep track of that so we can delete the edge later
|
||
|
|
if( Edge.ConnectedPolygons.Num() == 0 )
|
||
|
|
{
|
||
|
|
OrphanedEdgeIDs.Add( EdgeID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for( int32 HoleNumber = 0; HoleNumber < Polygon.HoleContours.Num(); ++HoleNumber )
|
||
|
|
{
|
||
|
|
GetPolygonHoleEdges( PolygonRef, HoleNumber, /* Out */ ContourEdgeIDs );
|
||
|
|
for( const FEdgeID EdgeID : ContourEdgeIDs )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
int32 ExistingPolygonNumber;
|
||
|
|
bool bWasFound = Edge.ConnectedPolygons.Find( PolygonRef, /* Out */ ExistingPolygonNumber );
|
||
|
|
|
||
|
|
check( bWasFound );
|
||
|
|
Edge.ConnectedPolygons.RemoveAt( ExistingPolygonNumber );
|
||
|
|
|
||
|
|
// If the edge has no more polygons connected, we'll keep track of that so we can delete the edge later
|
||
|
|
if( Edge.ConnectedPolygons.Num() == 0 )
|
||
|
|
{
|
||
|
|
OrphanedEdgeIDs.Add( EdgeID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Removes all of a polygon's triangles (including rendering triangles from the index buffer.)
|
||
|
|
DeletePolygonTriangles( PolygonRef );
|
||
|
|
|
||
|
|
// Delete our polygon's rendering vertices, as long as they aren't used by a different polygon.
|
||
|
|
// Imported meshes can share rendering vertices between polygons, if they have the same data (e.g. smooth edges and same UVs.)
|
||
|
|
{
|
||
|
|
// Delete the rendering vertices that are no longer used by any polygons in the mesh (if any)
|
||
|
|
|
||
|
|
// Check to see if we'll be orphaning any of the rendering vertices of this triangle. We can only delete rendering
|
||
|
|
// vertices that are not actually used by other polygons. Remember, any given vertex could have many rendering
|
||
|
|
// vertex copies (for discreet normals, etc.)
|
||
|
|
// @todo mesheditor holes: Also need to check holes? Maybe? Other polygons can't share those verts though... At the least,
|
||
|
|
// all of our hole rendering vertices need to be treated as orphans and deleted!
|
||
|
|
static TArray< FRenderingVertexID > OrphanedRenderingVertexIDs;
|
||
|
|
OrphanedRenderingVertexIDs.Reset();
|
||
|
|
|
||
|
|
for( int32 PerimeterVertexNumber = 0; PerimeterVertexNumber < Polygon.PerimeterContour.Vertices.Num(); ++PerimeterVertexNumber )
|
||
|
|
{
|
||
|
|
const FVertexID PerimeterVertexID = Polygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].VertexID;
|
||
|
|
const FRenderingVertexID PerimeterRenderingVertexID = Polygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].RenderingVertexID;
|
||
|
|
|
||
|
|
// Assume this rendering vertex is an orphan unless we find a connected polygon that is also using it
|
||
|
|
bool bIsOrphan = true;
|
||
|
|
for( const FEdgeID ConnectedEdgeID : Vertices[ PerimeterVertexID.GetValue() ].ConnectedEdgeIDs )
|
||
|
|
{
|
||
|
|
for( const FPolygonRef OtherPolygonRef : Edges[ ConnectedEdgeID.GetValue() ].ConnectedPolygons )
|
||
|
|
{
|
||
|
|
// Ignore ourselves
|
||
|
|
if( OtherPolygonRef != PolygonRef )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshPolygon& OtherPolygon = Sections[ OtherPolygonRef.SectionID.GetValue() ].Polygons[ OtherPolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
if( OtherPolygon.PerimeterContour.Vertices.ContainsByPredicate(
|
||
|
|
[ PerimeterRenderingVertexID ]( const FEditableStaticMeshPolygonContourVertex& V ) { return( PerimeterRenderingVertexID == V.RenderingVertexID ); }
|
||
|
|
))
|
||
|
|
{
|
||
|
|
bIsOrphan = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !bIsOrphan )
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( bIsOrphan )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshVertex& PerimeterVertex = Vertices[ PerimeterVertexID.GetValue() ];
|
||
|
|
verify( PerimeterVertex.RenderingVertexIDs.RemoveSingleSwap( PerimeterRenderingVertexID ) == 1 );
|
||
|
|
OrphanedRenderingVertexIDs.Add( PerimeterRenderingVertexID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( OrphanedRenderingVertexIDs.Num() )
|
||
|
|
{
|
||
|
|
DeleteOrphanRenderingVertices( OrphanedRenderingVertexIDs );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Delete the polygon
|
||
|
|
Section.Polygons.RemoveAt( PolygonRef.PolygonID.GetValue() );
|
||
|
|
|
||
|
|
|
||
|
|
// If we orphaned any edges and we were asked to delete those, then we'll go ahead and do that now.
|
||
|
|
// Deleting the edge may also delete orphaned vertices, if we were told to.
|
||
|
|
if( bDeleteOrphanedEdges && OrphanedEdgeIDs.Num() > 0 )
|
||
|
|
{
|
||
|
|
DeleteEdges( OrphanedEdgeIDs, bDeleteOrphanedVertices );
|
||
|
|
}
|
||
|
|
|
||
|
|
// If there are no longer any polygons left in the section, delete it too
|
||
|
|
if( bDeleteEmptySections && Section.Polygons.Num() == 0 )
|
||
|
|
{
|
||
|
|
DeleteSection( PolygonRef.SectionID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeletePolygonTriangles( const FPolygonRef PolygonRef )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
const int32 NumTrianglesToRemove = Polygon.TriangulatedPolygonTriangleIndices.Num();
|
||
|
|
if( NumTrianglesToRemove > 0 )
|
||
|
|
{
|
||
|
|
// Kill the polygon's rendering triangles in the static mesh
|
||
|
|
|
||
|
|
// Remove all of the polygon's triangles from our editable mesh's triangle list. While doing this, we'll keep
|
||
|
|
// track of all of the rendering mesh triangles that we'll need to remove later on. We'll also figure out which
|
||
|
|
// rendering vertices will need to be removed from their corresponding vertex
|
||
|
|
for( const FTriangleID TriangleIndexToRemove : Polygon.TriangulatedPolygonTriangleIndices )
|
||
|
|
{
|
||
|
|
// Remove this triangle from our editable mesh
|
||
|
|
Section.Triangles.RemoveAt( TriangleIndexToRemove.GetValue() );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update the index buffer by removing entries, and the rendering sections with new section counts
|
||
|
|
DeleteRenderingTrianglesForSectionTriangles( PolygonRef.SectionID, Polygon.TriangulatedPolygonTriangleIndices );
|
||
|
|
Polygon.TriangulatedPolygonTriangleIndices.Reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeleteRenderingTrianglesForSectionTriangles( const FSectionID SectionID, const TArray<FTriangleID>& SectionTriangleIDsToRemove )
|
||
|
|
{
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
const uint32 RenderingSectionIndex = Sections[ SectionID.GetValue() ].RenderingSectionIndex;
|
||
|
|
FStaticMeshSection& RenderingSection = StaticMeshLOD.Sections[ RenderingSectionIndex ];
|
||
|
|
|
||
|
|
|
||
|
|
const int32 NumTrianglesToRemove = SectionTriangleIDsToRemove.Num();
|
||
|
|
check( NumTrianglesToRemove > 0 );
|
||
|
|
|
||
|
|
// @todo mesheditor: We're simply changing existing triangles to be degenerates, so the section's total triangle count doesn't change.
|
||
|
|
// Later, when we compact data, we'll need to update the RenderingSection.NumTriangles (and RenderingSection.FirstIndex) for other
|
||
|
|
// sections, potentially.
|
||
|
|
|
||
|
|
// Update the index buffer by removing entries
|
||
|
|
{
|
||
|
|
// @todo mesheditor urgent: What about other index buffers in the mesh (DepthOnlyIndexBuffer, Wireframe, etc.) We need to remove our triangles from those too!
|
||
|
|
|
||
|
|
for( const FTriangleID SectionTriangleIDToRemove : SectionTriangleIDsToRemove )
|
||
|
|
{
|
||
|
|
const uint32 RenderingTriangleFirstVertexIndex = FEditableStaticMeshSection::TriangleIndexToRenderingTriangleFirstIndex( RenderingSection, SectionTriangleIDToRemove );
|
||
|
|
|
||
|
|
// Make the indices degenerate. We don't want to actually remove the indices from the index buffer, as that's can
|
||
|
|
// be a really slow operation. The mesh can be compacted later on to free up the memory.
|
||
|
|
for( int32 TriangleVertexNumber = 0; TriangleVertexNumber < 3; ++TriangleVertexNumber )
|
||
|
|
{
|
||
|
|
// @todo mesheditor subdiv: 0 could be outside of our section's MinVertexIndex/MaxVertexIndex range. We should choose the first valid
|
||
|
|
// rendering vertex index instead.
|
||
|
|
StaticMeshLOD.IndexBuffer.SetIndex( RenderingTriangleFirstVertexIndex + TriangleVertexNumber, 0 );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
inline const FStaticMeshLODResources& UEditableStaticMesh::GetStaticMeshLOD() const
|
||
|
|
{
|
||
|
|
const FStaticMeshRenderData& StaticMeshRenderData = *StaticMesh->RenderData;
|
||
|
|
const FStaticMeshLODResources& StaticMeshLOD = StaticMeshRenderData.LODResources[ SubMeshAddress.LODIndex ];
|
||
|
|
return StaticMeshLOD;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FStaticMeshLODResources& UEditableStaticMesh::GetStaticMeshLOD()
|
||
|
|
{
|
||
|
|
FStaticMeshRenderData& StaticMeshRenderData = *StaticMesh->RenderData;
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = StaticMeshRenderData.LODResources[ SubMeshAddress.LODIndex ];
|
||
|
|
return StaticMeshLOD;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool UEditableStaticMesh::DoesPolygonPerimeterVertexHaveDiscreetRenderingVertex( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber ) const
|
||
|
|
{
|
||
|
|
const FRenderingVertexID RenderingVertexID = GetPolygonPerimeterRenderingVertex( PolygonRef, PolygonVertexNumber );
|
||
|
|
|
||
|
|
bool bHasDiscreetRenderingVertex = true;
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshVertex& Vertex = Vertices[ GetPolygonPerimeterVertex( PolygonRef, PolygonVertexNumber ).GetValue() ];
|
||
|
|
for( int32 EdgeNumber = 0; EdgeNumber < Vertex.ConnectedEdgeIDs.Num(); ++EdgeNumber )
|
||
|
|
{
|
||
|
|
const FEdgeID EdgeID = Vertex.ConnectedEdgeIDs[ EdgeNumber ];
|
||
|
|
const FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
for( const FPolygonRef& ConnectedPolygonRef : Edge.ConnectedPolygons )
|
||
|
|
{
|
||
|
|
// Ignore ourselves for this check.
|
||
|
|
if( ConnectedPolygonRef != PolygonRef )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshPolygon& OtherPolygon = Sections[ ConnectedPolygonRef.SectionID.GetValue() ].Polygons[ ConnectedPolygonRef.PolygonID.GetValue() ];
|
||
|
|
if( OtherPolygon.PerimeterContour.Vertices.ContainsByPredicate(
|
||
|
|
[ RenderingVertexID ]( const FEditableStaticMeshPolygonContourVertex& V )
|
||
|
|
{
|
||
|
|
return( V.RenderingVertexID == RenderingVertexID );
|
||
|
|
}
|
||
|
|
))
|
||
|
|
{
|
||
|
|
// Oh no -- a different polygon is referencing our rendering vertex. We'll need to make a new one.
|
||
|
|
bHasDiscreetRenderingVertex = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !bHasDiscreetRenderingVertex )
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// @todo mesheditor debug
|
||
|
|
// UE_LOG( LogMeshEditingRuntime, Log, TEXT( "bHasDiscreetRenderingVertex=%i for Section=%i, Polygon=%i, PolyVertex=%i" ), bHasDiscreetRenderingVertex ? 1 : 0, PolygonRef.SectionID.GetIDValue(), PolygonRef.PolygonID.GetIDValue(), PolygonVertexNumber );
|
||
|
|
return bHasDiscreetRenderingVertex;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FRenderingVertexID UEditableStaticMesh::MakeDiscreetPolygonPerimeterRenderingVertexIfNeeded( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber )
|
||
|
|
{
|
||
|
|
const FVertexID VertexID = GetPolygonPerimeterVertex( PolygonRef, PolygonVertexNumber );
|
||
|
|
FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
|
||
|
|
FRenderingVertexID DiscreetRenderingVertexID = GetPolygonPerimeterRenderingVertex( PolygonRef, PolygonVertexNumber );
|
||
|
|
|
||
|
|
// Check to see which polygons are using this vertex. We need to make sure we have a unique rendering vertex for
|
||
|
|
// polygon vertex, because we don't want to affect other polygons with this change!
|
||
|
|
// @todo mesheditor urgent hole: Also need to do this for HOLES. HOLES really need a refactor...
|
||
|
|
if( !DoesPolygonPerimeterVertexHaveDiscreetRenderingVertex( PolygonRef, PolygonVertexNumber ) )
|
||
|
|
{
|
||
|
|
// Copy per-triangle vertex data (e.g. polygon perimeter vertex attributes) from the existing rendering vertex, so that we
|
||
|
|
// don't have to bother setting those manually after cloning the vertex.
|
||
|
|
const FRenderingVertexID CopyFromRenderingVertexID = DiscreetRenderingVertexID;
|
||
|
|
const FRenderingVertexID NewRenderingVertexID = AddNewRenderingVertexToPolygonPerimeter( PolygonRef, PolygonVertexNumber, CopyFromRenderingVertexID );
|
||
|
|
|
||
|
|
// Update our triangle index buffer. We need to point to our new vertex.
|
||
|
|
// @todo mesheditor: We only really need to update indices if the rendering vertex could have been shared between other triangles in the mesh. Currently this only happens with the initial rendering vertices from a converted static mesh. We currently always use discreet rendering vertices for each triangle while editing.
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
|
||
|
|
const uint32 RenderingSectionIndex = Section.RenderingSectionIndex;
|
||
|
|
FStaticMeshSection& RenderingSection = StaticMeshLOD.Sections[ RenderingSectionIndex ];
|
||
|
|
|
||
|
|
// For each of our triangulated triangles
|
||
|
|
for( const FTriangleID TriangleID : Polygon.TriangulatedPolygonTriangleIndices )
|
||
|
|
{
|
||
|
|
for( int32 VertexNumber = 0; VertexNumber < 3; ++VertexNumber )
|
||
|
|
{
|
||
|
|
if( Section.Triangles[ TriangleID.GetValue() ].RenderingVertexIDs[ VertexNumber ] == DiscreetRenderingVertexID )
|
||
|
|
{
|
||
|
|
// Update the triangle rendering vertex ID to the newly added rendering vertex
|
||
|
|
Section.Triangles[ TriangleID.GetValue() ].RenderingVertexIDs[ VertexNumber ] = NewRenderingVertexID;
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
const uint32 RenderingTriangleFirstIndex = FEditableStaticMeshSection::TriangleIndexToRenderingTriangleFirstIndex( RenderingSection, TriangleID );
|
||
|
|
const uint32 IndexOfIndex = RenderingTriangleFirstIndex + VertexNumber;
|
||
|
|
check( StaticMeshLOD.IndexBuffer.GetIndex( IndexOfIndex ) == DiscreetRenderingVertexID.GetValue() );
|
||
|
|
|
||
|
|
UpdateIndexBufferFormatIfNeeded( NewRenderingVertexID );
|
||
|
|
|
||
|
|
StaticMeshLOD.IndexBuffer.SetIndex( IndexOfIndex, NewRenderingVertexID.GetValue() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
DiscreetRenderingVertexID = NewRenderingVertexID;
|
||
|
|
|
||
|
|
// @todo mesheditor debug
|
||
|
|
//UE_LOG( LogMeshEditingRuntime, Log, TEXT( "Made DiscreetRenderingVertex=%i for Section=%i, Polygon=%i, PolyVertex=%i" ), DiscreetRenderingVertexID.GetValue(), PolygonRef.SectionID.GetValue(), PolygonRef.PolygonID.GetValue(), PolygonVertexNumber );
|
||
|
|
}
|
||
|
|
|
||
|
|
return DiscreetRenderingVertexID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FRenderingVertexID UEditableStaticMesh::AddNewRenderingVertexToPolygonPerimeter( const FPolygonRef PolygonRef, const int32 PolygonVertexNumber, const TOptional<FRenderingVertexID> OptionalCopyFromRenderingVertexID )
|
||
|
|
{
|
||
|
|
const FVertexID VertexID = GetPolygonPerimeterVertex( PolygonRef, PolygonVertexNumber );
|
||
|
|
|
||
|
|
static TArray<FVertexID> VertexIDs;
|
||
|
|
VertexIDs.Reset();
|
||
|
|
VertexIDs.Add( VertexID );
|
||
|
|
|
||
|
|
// OK, so we need a unique rendering vertex. Let's make one now. The vertex data will start "zeroed out".
|
||
|
|
static TArray<FRenderingVertexID> NewRenderingVertexIDs;
|
||
|
|
CreateRenderingVertices( VertexIDs, OptionalCopyFromRenderingVertexID, /* Out */ NewRenderingVertexIDs );
|
||
|
|
|
||
|
|
// Update our polygon
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ PolygonRef.SectionID.GetValue() ];
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Section.Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ].RenderingVertexID = NewRenderingVertexIDs[ 0 ]; // @todo mesheditor perf: This should really do multiple vertices at once (the issue stems from SetPolygonsVertexAttributes only doing one vertex at a time)
|
||
|
|
|
||
|
|
return NewRenderingVertexIDs[ 0 ];
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::SetEdgeVertices_Internal( const FEdgeID EdgeID, const FVertexID NewVertexID0, const FVertexID NewVertexID1 )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshEdge& Edge = Edges[ EdgeID.GetValue() ];
|
||
|
|
|
||
|
|
for( uint32 EdgeVertexNumber = 0; EdgeVertexNumber < 2; ++EdgeVertexNumber )
|
||
|
|
{
|
||
|
|
// Disconnect the edge from it's existing vertices
|
||
|
|
const FVertexID VertexID = Edge.VertexIDs[ EdgeVertexNumber ];
|
||
|
|
FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
verify( Vertex.ConnectedEdgeIDs.RemoveSingleSwap( EdgeID ) == 1 ); // Must have been already connected!
|
||
|
|
}
|
||
|
|
|
||
|
|
Edge.VertexIDs[0] = NewVertexID0;
|
||
|
|
Edge.VertexIDs[1] = NewVertexID1;
|
||
|
|
|
||
|
|
// Connect the new vertices to the edge
|
||
|
|
for( uint32 EdgeVertexNumber = 0; EdgeVertexNumber < 2; ++EdgeVertexNumber )
|
||
|
|
{
|
||
|
|
const FVertexID VertexID = Edge.VertexIDs[ EdgeVertexNumber ];
|
||
|
|
FEditableStaticMeshVertex& Vertex = Vertices[ VertexID.GetValue() ];
|
||
|
|
|
||
|
|
check( !Vertex.ConnectedEdgeIDs.Contains( EdgeID ) ); // Should not have already been connected
|
||
|
|
Vertex.ConnectedEdgeIDs.Add( EdgeID );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::InsertPolygonPerimeterVertices_Internal( const FPolygonRef PolygonRef, const int32 InsertBeforeVertexNumber, const TArray<FVertexAndAttributes>& VerticesToInsert )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Sections[ PolygonRef.SectionID.GetValue() ].Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
for( int32 InsertVertexIter = 0; InsertVertexIter < VerticesToInsert.Num(); ++InsertVertexIter )
|
||
|
|
{
|
||
|
|
Polygon.PerimeterContour.Vertices.Insert( FEditableStaticMeshPolygonContourVertex(), InsertBeforeVertexNumber + InsertVertexIter );
|
||
|
|
const FVertexAndAttributes& VertexToInsert = VerticesToInsert[ InsertVertexIter ];
|
||
|
|
|
||
|
|
const int32 PolygonVertexNumber = InsertBeforeVertexNumber + InsertVertexIter;
|
||
|
|
Polygon.PerimeterContour.Vertices[ PolygonVertexNumber ].VertexID = VertexToInsert.VertexID;
|
||
|
|
|
||
|
|
const FRenderingVertexID RenderingVertexID = AddNewRenderingVertexToPolygonPerimeter( PolygonRef, PolygonVertexNumber );
|
||
|
|
|
||
|
|
for( int32 AttributeNumber = 0; AttributeNumber < VertexToInsert.PolygonVertexAttributes.Attributes.Num(); ++AttributeNumber )
|
||
|
|
{
|
||
|
|
const FMeshElementAttributeData& MeshElementAttribute = VertexToInsert.PolygonVertexAttributes.Attributes[ AttributeNumber ];
|
||
|
|
SetPolygonPerimeterVertexAttribute_Internal( PolygonRef, InsertBeforeVertexNumber + InsertVertexIter, MeshElementAttribute.AttributeName, MeshElementAttribute.AttributeIndex, MeshElementAttribute.AttributeValue );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::RemovePolygonPerimeterVertices_Internal( const FPolygonRef PolygonRef, const int32 FirstVertexNumberToRemove, const int32 NumVerticesToRemove )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshPolygon& Polygon = Sections[ PolygonRef.SectionID.GetValue() ].Polygons[ PolygonRef.PolygonID.GetValue() ];
|
||
|
|
|
||
|
|
// @todo mesheditor: We're assuming these are all orphans because this function is only ever used to undo the addition of
|
||
|
|
// brand new vertices to existing polygons. If we ever start sharing rendering rendering vertices between mesh vertices
|
||
|
|
// with our mesh editing operations (not just imported meshes, like we do now), we'll need to do an orphan check here first.
|
||
|
|
static TArray<FRenderingVertexID> OrphanedRenderingVertexIDs;
|
||
|
|
OrphanedRenderingVertexIDs.Reset();
|
||
|
|
for( int32 VertexIter = 0; VertexIter < NumVerticesToRemove; ++VertexIter )
|
||
|
|
{
|
||
|
|
const int32 PerimeterVertexNumber = FirstVertexNumberToRemove + VertexIter;
|
||
|
|
const FRenderingVertexID RenderingVertexID = Polygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].RenderingVertexID;
|
||
|
|
FEditableStaticMeshVertex& PerimeterVertex = Vertices[ Polygon.PerimeterContour.Vertices[ PerimeterVertexNumber ].VertexID.GetValue() ];
|
||
|
|
verify( PerimeterVertex.RenderingVertexIDs.RemoveSingleSwap( RenderingVertexID ) == 1 );
|
||
|
|
OrphanedRenderingVertexIDs.Add( RenderingVertexID );
|
||
|
|
}
|
||
|
|
|
||
|
|
DeleteOrphanRenderingVertices( OrphanedRenderingVertexIDs );
|
||
|
|
|
||
|
|
Polygon.PerimeterContour.Vertices.RemoveAt( FirstVertexNumberToRemove, NumVerticesToRemove );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FSectionID UEditableStaticMesh::GetSectionIDFromMaterial_Internal( UMaterialInterface* Material, bool bCreateNewSectionIfNotFound )
|
||
|
|
{
|
||
|
|
check(StaticMesh);
|
||
|
|
// Iterate through the sections sparse array looking for an entry whose material index matches.
|
||
|
|
for( TSparseArray<FEditableStaticMeshSection>::TConstIterator It( Sections ); It; ++It )
|
||
|
|
{
|
||
|
|
const FEditableStaticMeshSection& Section = *It;
|
||
|
|
|
||
|
|
if( StaticMesh->GetMaterial( Section.MaterialIndex ) == Material )
|
||
|
|
{
|
||
|
|
return FSectionID( It.GetIndex() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we got here, the material index does not yet have a matching section.
|
||
|
|
if( bCreateNewSectionIfNotFound )
|
||
|
|
{
|
||
|
|
// @todo mesheditor: Currently new materials are added to the static mesh and never removed.
|
||
|
|
// They should be pruned every now and then, maybe when saving.
|
||
|
|
FSectionToCreate SectionToCreate;
|
||
|
|
SectionToCreate.Material = Material;
|
||
|
|
SectionToCreate.bEnableCollision = true;
|
||
|
|
SectionToCreate.bCastShadow = true;
|
||
|
|
|
||
|
|
return CreateSection( SectionToCreate );
|
||
|
|
}
|
||
|
|
|
||
|
|
return FSectionID::Invalid;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FSectionID UEditableStaticMesh::CreateSection_Internal( const FSectionToCreate& SectionToCreate )
|
||
|
|
{
|
||
|
|
const int32 MaterialIndex = StaticMesh->StaticMaterials.AddUnique( FStaticMaterial( SectionToCreate.Material ) );
|
||
|
|
|
||
|
|
uint32 LODSectionIndex = 0;
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
// Need to create a new rendering section. This is added to the end of the array.
|
||
|
|
// We also create a corresponding editable mesh section.
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
if( SectionToCreate.OriginalRenderingSectionIndex == INDEX_NONE )
|
||
|
|
{
|
||
|
|
// Add a new rendering section to the end if a specific index was not requested
|
||
|
|
LODSectionIndex = StaticMeshLOD.Sections.Emplace();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Otherwise add the rendering section at the specific index requested
|
||
|
|
LODSectionIndex = SectionToCreate.OriginalRenderingSectionIndex;
|
||
|
|
StaticMeshLOD.Sections.EmplaceAt( LODSectionIndex );
|
||
|
|
|
||
|
|
// Adjust rendering indices held by sections: any index above the one we just added now needs to be incremented.
|
||
|
|
for( FEditableStaticMeshSection& Section : Sections )
|
||
|
|
{
|
||
|
|
if( Section.RenderingSectionIndex > LODSectionIndex )
|
||
|
|
{
|
||
|
|
Section.RenderingSectionIndex++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FStaticMeshSection& StaticMeshSection = StaticMeshLOD.Sections[ LODSectionIndex ];
|
||
|
|
|
||
|
|
// Initially the section is empty, and it occupies zero elements in the index buffer.
|
||
|
|
// It is still placed in the correct location within the index buffer, immediately following the previous section,
|
||
|
|
// as it is a requirement that consecutive sections are placed contiguously in the index buffer.
|
||
|
|
// Determine the first index based on the index range of the previous rendering section.
|
||
|
|
if( LODSectionIndex == 0 )
|
||
|
|
{
|
||
|
|
StaticMeshSection.FirstIndex = 0;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
FStaticMeshSection& PreviousStaticMeshSection = StaticMeshLOD.Sections[ LODSectionIndex - 1 ];
|
||
|
|
const FSectionID SectionID = GetSectionForRenderingSectionIndex( LODSectionIndex - 1 );
|
||
|
|
check( SectionID != FSectionID::Invalid );
|
||
|
|
StaticMeshSection.FirstIndex = PreviousStaticMeshSection.FirstIndex + Sections[ SectionID.GetValue() ].MaxTriangles * 3;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill in the remaining rendering section properties.
|
||
|
|
StaticMeshSection.NumTriangles = 0;
|
||
|
|
StaticMeshSection.MinVertexIndex = 0;
|
||
|
|
StaticMeshSection.MaxVertexIndex = 0;
|
||
|
|
StaticMeshSection.bEnableCollision = SectionToCreate.bEnableCollision;
|
||
|
|
StaticMeshSection.bCastShadow = SectionToCreate.bCastShadow;
|
||
|
|
StaticMeshSection.MaterialIndex = MaterialIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy this information into the editable mesh section (which is authoritative)
|
||
|
|
int32 SectionIndex;
|
||
|
|
if( SectionToCreate.OriginalSectionID == FSectionID::Invalid )
|
||
|
|
{
|
||
|
|
// Add new section to the next free slot if a concrete index was not specified
|
||
|
|
SectionIndex = Sections.Add( FEditableStaticMeshSection() );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// If a concrete index was specified (should only be on undo), try to insert a new section there
|
||
|
|
SectionIndex = SectionToCreate.OriginalSectionID.GetValue();
|
||
|
|
check( !Sections.IsAllocated( SectionIndex ) );
|
||
|
|
Sections.Insert( SectionIndex, FEditableStaticMeshSection() );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill out the authoratitive section data
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ SectionIndex ];
|
||
|
|
Section.RenderingSectionIndex = LODSectionIndex;
|
||
|
|
Section.MaterialIndex = MaterialIndex;
|
||
|
|
Section.bEnableCollision = SectionToCreate.bEnableCollision;
|
||
|
|
Section.bCastShadow = SectionToCreate.bCastShadow;
|
||
|
|
Section.MaxTriangles = 0;
|
||
|
|
|
||
|
|
const FSectionID SectionID( SectionIndex );
|
||
|
|
|
||
|
|
// Allow operation to be undone
|
||
|
|
FDeleteSectionChangeInput DeleteSectionChangeInput;
|
||
|
|
DeleteSectionChangeInput.SectionID = SectionID;
|
||
|
|
|
||
|
|
AddUndo( MakeUnique<FDeleteSectionChange>( DeleteSectionChangeInput ) );
|
||
|
|
|
||
|
|
return SectionID;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void UEditableStaticMesh::DeleteSection_Internal( const FSectionID SectionID )
|
||
|
|
{
|
||
|
|
FEditableStaticMeshSection& Section = Sections[ SectionID.GetValue() ];
|
||
|
|
|
||
|
|
// Prepare the change input struct
|
||
|
|
FSectionToCreate SectionToCreate;
|
||
|
|
SectionToCreate.Material = StaticMesh->StaticMaterials[ Section.MaterialIndex ].MaterialInterface;
|
||
|
|
SectionToCreate.bEnableCollision = Section.bEnableCollision;
|
||
|
|
SectionToCreate.bCastShadow = Section.bCastShadow;
|
||
|
|
SectionToCreate.OriginalSectionID = SectionID;
|
||
|
|
|
||
|
|
FCreateSectionChangeInput CreateSectionChangeInput;
|
||
|
|
CreateSectionChangeInput.SectionToCreate = SectionToCreate;
|
||
|
|
|
||
|
|
// Remove material slot associated with section
|
||
|
|
// @todo mesheditor: can more than one section share a material? Mesh editor currently assumes not, but this will break anything which does
|
||
|
|
const int32 MaterialIndex = Sections[ SectionID.GetValue() ].MaterialIndex;
|
||
|
|
StaticMesh->StaticMaterials.RemoveAt( MaterialIndex );
|
||
|
|
|
||
|
|
for( FEditableStaticMeshSection& SectionToAdjust : Sections )
|
||
|
|
{
|
||
|
|
if( SectionToAdjust.MaterialIndex > MaterialIndex )
|
||
|
|
{
|
||
|
|
SectionToAdjust.MaterialIndex--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Adjust rendering indices held by sections: any index above the one we just deleted now needs to be decremented.
|
||
|
|
const uint32 RenderingSectionIndex = Section.RenderingSectionIndex;
|
||
|
|
|
||
|
|
for( FEditableStaticMeshSection& SectionToAdjust : Sections )
|
||
|
|
{
|
||
|
|
if( SectionToAdjust.RenderingSectionIndex > RenderingSectionIndex )
|
||
|
|
{
|
||
|
|
SectionToAdjust.RenderingSectionIndex--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( !IsPreviewingSubdivisions() )
|
||
|
|
{
|
||
|
|
FStaticMeshLODResources& StaticMeshLOD = GetStaticMeshLOD();
|
||
|
|
const uint32 FirstIndex = StaticMeshLOD.Sections[ RenderingSectionIndex ].FirstIndex;
|
||
|
|
|
||
|
|
// Get current number of triangles allocated for this section
|
||
|
|
const int32 MaxTriangles = Section.MaxTriangles;
|
||
|
|
|
||
|
|
// Remove indices from this poisition in the index buffer
|
||
|
|
StaticMeshLOD.IndexBuffer.RemoveIndicesAt( FirstIndex, MaxTriangles * 3 );
|
||
|
|
|
||
|
|
// Adjust first index for all subsequent render sections to account for the indices just removed.
|
||
|
|
// It is guaranteed that index buffer indices are in the same order as the rendering sections.
|
||
|
|
const uint32 NumRenderingSections = StaticMeshLOD.Sections.Num();
|
||
|
|
for( uint32 Index = RenderingSectionIndex + 1; Index < NumRenderingSections; ++Index )
|
||
|
|
{
|
||
|
|
check( StaticMeshLOD.Sections[ Index ].FirstIndex >= FirstIndex );
|
||
|
|
StaticMeshLOD.Sections[ Index ].FirstIndex -= MaxTriangles * 3;
|
||
|
|
}
|
||
|
|
|
||
|
|
for( uint32 Index = 0; Index < NumRenderingSections; ++Index )
|
||
|
|
{
|
||
|
|
FStaticMeshSection& StaticMeshSection = StaticMeshLOD.Sections[ Index ];
|
||
|
|
if( StaticMeshSection.MaterialIndex > MaterialIndex )
|
||
|
|
{
|
||
|
|
StaticMeshSection.MaterialIndex--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
StaticMeshLOD.Sections.RemoveAt( RenderingSectionIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove the section from the sparse array
|
||
|
|
Sections.RemoveAt( SectionID.GetValue() );
|
||
|
|
|
||
|
|
AddUndo( MakeUnique<FCreateSectionChange>( CreateSectionChangeInput ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
FSectionID UEditableStaticMesh::GetSectionForRenderingSectionIndex( const int32 RenderingSectionIndex ) const
|
||
|
|
{
|
||
|
|
for( auto It = Sections.CreateConstIterator(); It; ++It )
|
||
|
|
{
|
||
|
|
if( It->RenderingSectionIndex == RenderingSectionIndex )
|
||
|
|
{
|
||
|
|
return FSectionID( It.GetIndex() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return FSectionID::Invalid;
|
||
|
|
}
|