// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCOE/SMutableMeshViewer.h" #include "Engine/SkeletalMesh.h" #include "Framework/Views/TableViewMetadata.h" #include "MuCOE/SMutableMeshViewport.h" #include "MuCOE/SMutableSkeletonViewer.h" #include "MuCO/CustomizableObject.h" #include "MuT/TypeInfo.h" #include "MuR/MeshPrivate.h" #include "PropertyCustomizationHelpers.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Views/SListView.h" class ITableRow; class STableViewBase; class SWidget; class USkeleton; #define LOCTEXT_NAMESPACE "CustomizableObjectEditor" /** Namespace containing the IDs for the header on the buffer's channels list */ namespace MutableBufferChannelsListColumns { static const FName ChannelSemanticIndexColumnID("Channel Semantic Index"); static const FName ChannelSemanticColumnID("Channel Semantic"); static const FName ChannelFormatColumnID("Format"); static const FName ChannelComponentCountID("Components"); }; /* Row element generated on the buffers list. It represents the UI side of the Buffers data**/ class SMutableMeshBufferChannelListRow : public SMultiColumnTableRow> { public: void Construct(const FArguments& Args, const TSharedRef& InOwnerTableView, const TSharedPtr& InRowItem) { RowItem = InRowItem; SMultiColumnTableRow>::Construct( STableRow::FArguments() .ShowSelection(true) , InOwnerTableView ); } virtual TSharedRef GenerateWidgetForColumn(const FName& InColumnName) override { // Column with the index for the buffer . // Useful for knowing the channels on what buffer reside if (InColumnName == MutableBufferChannelsListColumns::ChannelSemanticIndexColumnID) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(4,0) [ SNew(STextBlock). Text(RowItem->SemanticIndex) ]; } // Column with the name for the buffer (Semantic of the buffer) if (InColumnName == MutableBufferChannelsListColumns::ChannelSemanticColumnID) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock). Text(RowItem->BufferSemantic) ]; } // Column with the format of the buffer if (InColumnName == MutableBufferChannelsListColumns::ChannelFormatColumnID) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock). Text(RowItem->BufferFormat) ]; } // Column with Buffer component count if (InColumnName == MutableBufferChannelsListColumns::ChannelComponentCountID) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock). Text(RowItem->BufferComponentCount) ]; } // Invalid column name so no widget will be produced return SNullWidget::NullWidget; } private: TSharedPtr RowItem; }; /** Namespace containing the IDs for the header on the buffers list */ namespace MutableMeshBuffersListColumns { static const FName BufferIndexColumnID("Buffer Index"); static const FName BufferChannelsColumnID("Channels"); } class SMutableMeshBufferListRow : public SMultiColumnTableRow> { public: void Construct(const FArguments& Args, const TSharedRef& InOwnerTableView, const TSharedPtr& InRowItem, TSharedPtr InHost) { HostMutableMeshViewer = InHost; RowItem = InRowItem; SMultiColumnTableRow>::Construct( STableRow::FArguments() .ShowSelection(true) , InOwnerTableView ); } virtual TSharedRef GenerateWidgetForColumn(const FName& InColumnName) override { // Column with the index for the buffer . // Useful for knowing the channels on what buffer reside if (InColumnName == MutableMeshBuffersListColumns::BufferIndexColumnID) { return SNew(SBorder) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Center) .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(STextBlock). Text(RowItem->BufferIndex) ] ]; } // Generate the sub table here if (InColumnName == MutableMeshBuffersListColumns::BufferChannelsColumnID) { const TSharedRef GeneratedChannelList = HostMutableMeshViewer->GenerateBufferChannelsListView(RowItem->BufferChannels); return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ GeneratedChannelList ]; } // Invalid column name so no widget will be produced return SNullWidget::NullWidget; } private: TSharedPtr RowItem; TSharedPtr HostMutableMeshViewer; }; void SMutableMeshViewer::Construct(const FArguments& InArgs) { // Splitter values constexpr float TablesSplitterValue = 0.5f; constexpr float ViewportSplitterValue = 0.5f; ChildSlot [ SNew(SSplitter) + SSplitter::Slot() .Value(TablesSplitterValue) [ GenerateDataTableSlates() ] + SSplitter::Slot() .Value(ViewportSplitterValue) [ GenerateViewportSlates() ] ]; // If a mesh has been provided then do set the mesh for this object if (InArgs._Mesh) { SetMesh(InArgs._Mesh); } } void SMutableMeshViewer::SetMesh(const TSharedPtr& InMesh) { if (InMesh != MutableMesh) { MutableMesh = InMesh; // Extract a copy of the tags int32 TagCount = MutableMesh ? MutableMesh->GetTagCount() : 0; MeshTagList.SetNum(TagCount, EAllowShrinking::No); for (int32 TagIndex=0; TagIndex(MutableMesh->GetTag(TagIndex)); } if (MutableMesh) { OnMeshChanged(); // Skeleton slate viewer update if (TSharedPtr MutableSkeleton = MutableMesh->GetSkeleton()) { MutableSkeletonViewer->SetSkeleton(MutableSkeleton); MutableSkeletonViewer->SetVisibility(EVisibility::Visible); } else { MutableSkeletonViewer->SetVisibility(EVisibility::Hidden); } FString DebugLog; MutableMesh->Log(DebugLog, 8); UE_LOG(LogMutable, Warning, TEXT("[%s]"), *DebugLog); } MeshViewport->SetMesh(MutableMesh); } } TSharedRef SMutableMeshViewer::GenerateViewportSlates() { TSharedRef Container = SNew(SVerticalBox) // User warning messages // + SVerticalBox::Slot() // .AutoHeight() // [ // // TODO: Add a message to tell the user why no mesh is being displayed // SNew(SWarningOrErrorBox) // .MessageStyle(EMessageStyle::Warning) // .Message(FText::FromString(FString("This is just a simulation"))) // ] // Mesh Drawing space + SVerticalBox::Slot() [ SAssignNew(MeshViewport, SMutableMeshViewport) .Mesh(MutableMesh) ]; return Container; } TSharedRef SMutableMeshViewer::GenerateDataTableSlates() { // Formatting constexpr int32 IndentationSpace = 16; constexpr int32 SimpleSpacing = 1; constexpr int32 AfterTitleSpacing = 4; constexpr int32 EndOfSectionSpacing = 12; // Naming const FText GeneralDataTitle = LOCTEXT("GeneralDataTitle", "General Data"); const FText VerticesCountTitle = LOCTEXT("VerticesCountTitle", "Vertex count : "); const FText FacesCountTitle = LOCTEXT("FacesCountTitle", "Face count : "); const FText BonesCountTitle = LOCTEXT("BonesCountTitle", "Bone count : "); const FText MeshIdPrefixTitle = LOCTEXT("MeshIdPrefixTitle", "Mesh ID prefix : "); const FText MeshFlagsTitle = LOCTEXT("MeshFlagsTitle", "Mesh flags : "); const FText BuffersTitle = LOCTEXT("BuffersTitle", "Buffers"); return SNew(SScrollBox) + SScrollBox::Slot() [ SNew(SVerticalBox) // General data ---------------------------------------------------------------- + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock). Text(GeneralDataTitle) ] + SVerticalBox::Slot(). Padding(IndentationSpace, AfterTitleSpacing). AutoHeight() [ SNew(SVerticalBox) // Vertices + SVerticalBox::Slot(). Padding(0, SimpleSpacing). AutoHeight() [ SNew(SHorizontalBox) // Vertices title + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(VerticesCountTitle) ] // Vertices value + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(this, &SMutableMeshViewer::GetVertexCount) ] ] // Faces + SVerticalBox::Slot(). Padding(0, SimpleSpacing). AutoHeight() [ SNew(SHorizontalBox) // Faces title + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(FacesCountTitle) ] // Faces Value + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(this, &SMutableMeshViewer::GetFaceCount) ] ] // Bones + SVerticalBox::Slot(). Padding(0, SimpleSpacing). AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(BonesCountTitle) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(this, &SMutableMeshViewer::GetBoneCount) ] ] // Vertex ID + SVerticalBox::Slot(). Padding(0, SimpleSpacing). AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(MeshIdPrefixTitle) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(this, &SMutableMeshViewer::GetMeshIdPrefix) ] ] // Flags + SVerticalBox::Slot(). Padding(0, SimpleSpacing). AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(MeshFlagsTitle) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock). Text(this, &SMutableMeshViewer::GetMeshFlags) ] ] ] // Buffers Data -------------------------------------------------------------- + SVerticalBox::Slot() .Padding(0, EndOfSectionSpacing) .AutoHeight() [ SNew(SVerticalBox) + SVerticalBox::Slot(). AutoHeight() [ // Buffers data Title SNew(STextBlock). Text(BuffersTitle) ] + SVerticalBox::Slot() .Padding(IndentationSpace, AfterTitleSpacing) .AutoHeight() [ SNew(SVerticalBox) // List of vertex buffers ---------- + SVerticalBox::Slot() .AutoHeight() [ GenerateBuffersListView( VertexBuffersSlateView, VertexBuffers, FText(LOCTEXT("VertexBufferType", "Vertex"))) ] // --------------------------------- ] + SVerticalBox::Slot() .Padding(IndentationSpace, 6) .AutoHeight() [ SNew(SVerticalBox) // List of Index buffers ---------- + SVerticalBox::Slot() .AutoHeight() [ GenerateBuffersListView( IndexBuffersSlateView, IndexBuffers, FText(LOCTEXT("IndexBufferType", "Index"))) ] // --------------------------------- ] ] // Bones data ---------------------------------------------------------------- + SVerticalBox::Slot() .Padding(0, EndOfSectionSpacing) .AutoHeight() [ SAssignNew(MutableSkeletonViewer, SMutableSkeletonViewer) ] // --------------------------------- // Tags + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock). Text(LOCTEXT("Tags", "Tags")) ] + SVerticalBox::Slot() .Padding(IndentationSpace, AfterTitleSpacing) .AutoHeight() [ SNew(SListView< TSharedPtr >) .ListItemsSource(&MeshTagList) .OnGenerateRow(this, &SMutableMeshViewer::GenerateTagRow) ] // --------------------------------- // UVs +SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock). Text(LOCTEXT("UVs", "UVs")) ] + SVerticalBox::Slot() .Padding(IndentationSpace, AfterTitleSpacing) .AutoHeight() .MinHeight(300.0f) [ SAssignNew(LayoutGridWidget, SCustomizableObjectLayoutGrid) .Mode(ELGM_Show) .GridSize(this, &SMutableMeshViewer::GetGridSize) .Blocks(this, &SMutableMeshViewer::GetBlocks) .UVLayout(this, &SMutableMeshViewer::GetUVs) ] // --------------------------------- ]; } FIntPoint SMutableMeshViewer::GetGridSize() const { // TODO return FIntPoint(8,8); } TArray SMutableMeshViewer::GetBlocks() const { TArray Blocks; // TODO return Blocks; } TArray SMutableMeshViewer::GetUVs() const { TArray Lines; if (MutableMesh && !MutableMesh->VertexBuffers.IsDescriptor()) { int32 NumFaces = MutableMesh->GetFaceCount(); Lines.Reserve(NumFaces*6); // TODO int32 UVIndex = 0; mu::UntypedMeshBufferIteratorConst ItIndices(MutableMesh->IndexBuffers, mu::EMeshBufferSemantic::VertexIndex, 0); mu::UntypedMeshBufferIteratorConst It(MutableMesh->VertexBuffers, mu::EMeshBufferSemantic::TexCoords, UVIndex); for (int32 FaceIndex=0; FaceIndex SMutableMeshViewer::GenerateTagRow(TSharedPtr InItem, const TSharedRef& OwnerTable) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock).Text(FText::FromString(*InItem)) ]; } TSharedRef SMutableMeshViewer::GenerateBuffersListView( TSharedPtr>>& OutHostListView, const TArray>& InBufferElements, const FText& InBufferSetTypeName) { // Headers const FText BufferIndexTitle = FText(LOCTEXT("BufferIndexTitle", "Buffer")); const FText BufferChannelsTitle = FText::Format( LOCTEXT("NumberOfBufferChannels", "{0} Buffer Channels"), InBufferSetTypeName); // Tooltips const FText BufferIndexTooltip = FText(LOCTEXT("BufferIndexTooltip", "Represents the index where the mutable buffer is found inside the buffer set")); const FText BufferChannelsTooltip = FText(LOCTEXT("BufferChannelsTooltip", "The channels contained inside each mutable buffer.")); return SAssignNew(OutHostListView, SListView>) .ListItemsSource(&InBufferElements) .OnGenerateRow(this, &SMutableMeshViewer::OnGenerateBufferRow) .SelectionMode(ESelectionMode::None) .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column(MutableMeshBuffersListColumns::BufferIndexColumnID) .DefaultTooltip(BufferIndexTooltip) .DefaultLabel(BufferIndexTitle) .FillWidth(0.1f) + SHeaderRow::Column(MutableMeshBuffersListColumns::BufferChannelsColumnID) .DefaultTooltip(BufferChannelsTooltip) .DefaultLabel(BufferChannelsTitle) .FillWidth(0.9f) ); } TSharedRef SMutableMeshViewer::GenerateBufferChannelsListView( const TSharedPtr>>& InBufferChannelElements) { // Headers const FText ChannelIndex = FText(LOCTEXT("ChannelIndexTitle", "Index")); const FText ChannelSemanticTitle = FText(LOCTEXT("SemanticLabelTitle", "Semantic")); const FText ChannelFormatTitle = FText(LOCTEXT("FormatLabelTitle", "Format")); const FText ComponentCountTitle = FText(LOCTEXT("ComponentCountLabelTitle", "Components")); // Tooltips const FText ChannelIndexTooltip = FText(LOCTEXT("ChannelIndexTooltip","Represents the SemanticIndex of the mutable channel inside the whole buffer set. Usefull when more than one channel does share the same type.")); const FText ChannelSemanticTooltip = FText(LOCTEXT("ChannelSemanticTooltip","The semantic that identifies this channel.")); const FText ChannelFormatTooltip = FText(LOCTEXT("ChannelFormatTooltip","The format of the data being held.")); const FText ComponentCountTooltip = FText(LOCTEXT("ChannelComponentTooltip","The amount of components each unit of data has.")); return SNew(SListView>) .ListItemsSource(InBufferChannelElements.Get()) .OnGenerateRow(this, &SMutableMeshViewer::OnGenerateBufferChannelRow) .SelectionMode(ESelectionMode::None) .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column(MutableBufferChannelsListColumns::ChannelSemanticIndexColumnID) .DefaultTooltip(ChannelIndexTooltip) .DefaultLabel(ChannelIndex) .FillWidth(0.14f) + SHeaderRow::Column(MutableBufferChannelsListColumns::ChannelSemanticColumnID) .DefaultTooltip(ChannelSemanticTooltip) .DefaultLabel(ChannelSemanticTitle) .FillWidth(0.35f) + SHeaderRow::Column(MutableBufferChannelsListColumns::ChannelFormatColumnID) .DefaultTooltip(ChannelFormatTooltip) .DefaultLabel(ChannelFormatTitle) .FillWidth(0.65f) + SHeaderRow::Column(MutableBufferChannelsListColumns::ChannelComponentCountID) .DefaultTooltip(ComponentCountTooltip) .DefaultLabel(ComponentCountTitle) .FillWidth(0.3f) ); } void SMutableMeshViewer::OnMeshChanged() { // Cache the data accessible from the mu::FMesh to be later used by the UI FillTargetBufferSetDataArray(MutableMesh->GetVertexBuffers(), VertexBuffers, VertexBuffersSlateView); FillTargetBufferSetDataArray(MutableMesh->GetIndexBuffers(), IndexBuffers, IndexBuffersSlateView); // Restore the widths of the columns each time the mesh gets changed. VertexBuffersSlateView->GetHeaderRow()->ResetColumnWidths(); IndexBuffersSlateView->GetHeaderRow()->ResetColumnWidths(); } void SMutableMeshViewer::FillTargetBufferSetDataArray(const mu::FMeshBufferSet& InMuMeshBufferSet, TArray>& OutBuffersDataArray, TSharedPtr>>& InHostListView) { // Make sure no data is left from previous runs OutBuffersDataArray.Empty(); // Iterate over the buffers and get the type and semantic const int32 BuffersCount = InMuMeshBufferSet.GetBufferCount(); for (int32 BufferIndexOnBufferSet = 0; BufferIndexOnBufferSet < BuffersCount; BufferIndexOnBufferSet++) { // Object tasked with the displaying of all the channels it is made with TSharedPtr NewBufferDefinition = MakeShareable(new FBufferElement()); // Array with the data from all channels found inside the current buffer object TSharedPtr>> ChannelsArray = MakeShareable(new TArray>); // Get the channels the buffer has const int32 ChannelsOnBuffer = InMuMeshBufferSet.GetBufferChannelCount(BufferIndexOnBufferSet); if (ChannelsOnBuffer == 0) { // Add a new row telling the user no channels are set on the buffer const TSharedPtr NewChannelDefinition = MakeShareable(new FBufferChannelElement()); NewChannelDefinition->BufferSemantic = FText(INVTEXT("No Channels found...")); ChannelsArray->Add(NewChannelDefinition); NewBufferDefinition->BufferChannels = ChannelsArray; continue; } // Load all channels found onto our array of channels to be later displayed by the UI for (int32 ChannelIndexOnBuffer = 0; ChannelIndexOnBuffer < ChannelsOnBuffer; ChannelIndexOnBuffer++) { TSharedPtr NewChannelDefinition = MakeShareable(new FBufferChannelElement()); mu::EMeshBufferSemantic BufferChannelSemantic = mu::EMeshBufferSemantic::None; mu::EMeshBufferFormat BufferFormat = mu::EMeshBufferFormat::None; int32 BufferComponentCount = 0; int32 SemanticIndex = -1; // Get the data from mutable that we require from the selected buffer set buffer InMuMeshBufferSet.GetChannel ( BufferIndexOnBufferSet, ChannelIndexOnBuffer, &(BufferChannelSemantic), &(SemanticIndex), &(BufferFormat), &(BufferComponentCount), nullptr ); // In order to get the indexes for the UI display, cast the value of the enumeration to int const int32 SemanticTypeIndex = static_cast(BufferChannelSemantic); const int32 BufferFormatIndex = static_cast(BufferFormat); // Using mu::TypeInfo find what is the name of the buffer semantic and the buffer format NewChannelDefinition->SemanticIndex = FText::AsNumber(SemanticIndex); NewChannelDefinition->BufferSemantic = FText::FromString( *FString(mu::TypeInfo::s_meshBufferSemanticName[SemanticTypeIndex])); NewChannelDefinition->BufferFormat = FText::FromString( *FString(mu::TypeInfo::s_meshBufferFormatName[BufferFormatIndex])); NewChannelDefinition->BufferComponentCount = FText::FromString(*FString::FromInt(BufferComponentCount)); ChannelsArray->Add(NewChannelDefinition); } NewBufferDefinition->BufferIndex = FText::AsNumber( BufferIndexOnBufferSet); NewBufferDefinition->BufferChannels = ChannelsArray; OutBuffersDataArray.Add(NewBufferDefinition); } // If no data has been found ad an element to show it if (OutBuffersDataArray.IsEmpty()) { const TSharedPtr NewBufferDefinition = MakeShareable(new FBufferElement()); NewBufferDefinition->BufferIndex = FText(INVTEXT("N/A")); const TSharedPtr NewChannelDefinition = MakeShareable(new FBufferChannelElement()); NewChannelDefinition->BufferSemantic = FText(INVTEXT("No buffers found...")); TSharedPtr>> ChannelsArray = MakeShareable(new TArray>); ChannelsArray->Add(NewChannelDefinition); NewBufferDefinition->BufferChannels = ChannelsArray; OutBuffersDataArray.Add(NewBufferDefinition); } // Make sure the list gets refreshed with the new contents InHostListView->RequestListRefresh(); } FText SMutableMeshViewer::GetVertexCount() const { return FText::AsNumber(MutableMesh ? MutableMesh->GetVertexCount() : 0); } FText SMutableMeshViewer::GetFaceCount() const { return FText::AsNumber(MutableMesh ? MutableMesh->GetFaceCount() : 0); } FText SMutableMeshViewer::GetBoneCount() const { return FText::AsNumber(MutableMesh && MutableMesh->GetSkeleton() ? MutableMesh->GetSkeleton()->GetBoneCount() : 0); } FText SMutableMeshViewer::GetMeshIdPrefix() const { return FText::AsNumber(MutableMesh ? MutableMesh->MeshIDPrefix : 0); } FText SMutableMeshViewer::GetMeshFlags() const { return FText::AsNumber(MutableMesh ? uint32(MutableMesh->Flags) : uint32(0)); } TSharedRef SMutableMeshViewer::OnGenerateBufferRow(TSharedPtr InBuffer, const TSharedRef& OwnerTable) { TSharedRef Row = SNew(SMutableMeshBufferListRow, OwnerTable, InBuffer, SharedThis(this) ); return Row; } TSharedRef SMutableMeshViewer::OnGenerateBufferChannelRow(TSharedPtr InBufferChannel, const TSharedRef& OwnerTable) { TSharedRef Row = SNew(SMutableMeshBufferChannelListRow, OwnerTable, InBufferChannel); return Row; } #undef LOCTEXT_NAMESPACE