#pragma region CPL License /* Nuclex Unreal Module Copyright (C) 2014-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #include "UI/ResizableDialogMeshComponent.h" #include "NuclexErrors.h" #include // --------------------------------------------------------------------------------------------- // UResizableDialogMeshComponent::UResizableDialogMeshComponent( const FObjectInitializer &objectInitializer ) : UProceduralMeshComponent(objectInitializer), MeshTemplate(nullptr), clientAreaSize(160.0f, 90.0f) {} // --------------------------------------------------------------------------------------------- // FVector2D UResizableDialogMeshComponent::GetDialogBoxSize() const { return this->clientAreaSize; } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::SetDialogBoxSize(const FVector2D &newSize) { // If we were unable to copy a dialog mesh for some reason, do nothing here if(this->originalVertices.Num() == 0) { return; } // Calculate the necessary adjustment (we assume symmetry) to make // the UI area the size requested by the caller FVector2D originalClientSize = this->innerBounds.GetSize(); float widthAdjustment = (newSize.X - originalClientSize.X) / 2.0f; float heightAdjustment = (newSize.Y - originalClientSize.Y) / 2.0f; // Move the vertices of the four quadrants closer or further apart as needed // so that they match the requested client area size int32 meshSectionCount = GetNumSections(); for(int32 index = 0; index < meshSectionCount; ++index) { TArray &originalVertexBuffer = this->originalVertices[index]; FProcMeshSection §ion = *GetProcMeshSection(index); TArray &vertexBuffer = section.ProcVertexBuffer; int32 vertexCount = vertexBuffer.Num(); for(int32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { float x = originalVertexBuffer[vertexIndex].Position.Y; float y = originalVertexBuffer[vertexIndex].Position.Z; if(x < this->center.X) { x -= widthAdjustment; } else { x += widthAdjustment; } if(y < this->center.Y) { y -= heightAdjustment; } else { y += heightAdjustment; } vertexBuffer[vertexIndex].Position.Y = x; vertexBuffer[vertexIndex].Position.Z = y; } } // Vertices have been updated, make sure our vertex buffer gets updated forceVertexBufferUpdate(); this->clientAreaSize = newSize; } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::BeginPlay() { Super::BeginPlay(); // If the user didn't set a mesh template, there's nothing we can do if(!IsValid(this->MeshTemplate)) { UE_LOG( LogNuclex, Warning, TEXT("No mesh template set, dialog box cannot be displayed") ); return; } // Also, the mesh must be in a CPU-accessible format if(!this->MeshTemplate->bAllowCPUAccess) { UE_LOG( LogNuclex, Warning, TEXT("Mesh template does not allow CPU mesh access, dialog box cannot be displayed") ); return; } // Copy the template mesh into ourselves as a UProceduralMeshComponent copyVerticesFromStaticMeshTemplate(); backupOriginalVertices(); calculateMeshMetrics(); // Finally, resize the dialog to the currently requested client area size FVector2D originalClientAreaSize = this->innerBounds.GetSize(); if(originalClientAreaSize != this->clientAreaSize) { SetDialogBoxSize(this->clientAreaSize); } } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::EndPlay(const EEndPlayReason::Type endPlayReason) { // Make sure we're not building up more and more copies of the dialog box vertices // when the actor gets reused during multiple PIE sessions this->originalVertices.Reset(); ClearAllMeshSections(); Super::EndPlay(endPlayReason); } // --------------------------------------------------------------------------------------------- // #if defined(THE_EASY_WAY) // Not sure if this rubs the UE GC in a bad way :-/ const int32 LodIndex = 0; // Always use LOD 0, which has the most detail // Create a temporary static mesh component using the user-assigned static mesh and // use a convenient helper method to transfer its data into our procedural mesh component { TUniquePtr staticMeshComponent( NewObject() ); staticMeshComponent->SetStaticMesh(this->MeshTemplate); UKismetProceduralMeshLibrary::CopyProceduralMeshFromStaticMeshComponent( &staticMeshComponent, LodIndex, this, false // don't create collision mesh ); } #endif void UResizableDialogMeshComponent::copyVerticesFromStaticMeshTemplate() { const int32 LodIndex = 0; // Always use LOD 0, which has the most detail // Let's get defensive, if there's no mesh data for some reason, warn instead of crashing if(this->MeshTemplate->RenderData == nullptr) { UE_LOG( LogNuclex, Warning, TEXT("Mesh template is lacking render data. Empty static mesh selected?") ); return; } // Make sure the LOD we want to read from exists int lodCount = this->MeshTemplate->RenderData->LODResources.Num(); if(lodCount <= LodIndex) { UE_LOG( LogNuclex, Warning, TEXT("Mesh template has no LODs set up. At least one LOD is needed for vertex data") ); return; } // Copy the mesh sections from the static mesh. This code is nearly identical to what // can be found in UKismetProceduralMeshLibrary::CopyProceduralMeshFromStaticMeshComponent(), // but that method would requires us to create a static mesh component. { const FStaticMeshLODResources &lod = this->MeshTemplate->RenderData->LODResources[LodIndex]; // We only need the indices this one time for copying TArray indices; int32 sectionCount = lod.Sections.Num(); for(int32 sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) { // Fetch the vertex data and triangle indices for the current mesh section UKismetProceduralMeshLibrary::GetSectionFromStaticMesh( this->MeshTemplate, 0, // LOD index sectionIndex, this->vertices, indices, this->normals, this->uvs, this->tangents ); // And recreate the mesh section in the procedural mesh's buffers CreateMeshSection_LinearColor( sectionIndex, this->vertices, indices, this->normals, this->uvs, colors, this->tangents, false // don't create collision ); // Reset the arrays in case UKismetProceduralMeshLibrary::GetSectionFromStaticMesh() // appends blindly. Defensive programming this->vertices.Reset(); indices.Reset(); this->normals.Reset(); this->uvs.Reset(); this->tangents.Reset(); this->colors.Reset(); } // for each mesh section } // beauty scope // Copy the materials from the static mesh { int32 materialCount = this->MeshTemplate->StaticMaterials.Num(); for(int32 materialIndex = 0; materialIndex < materialCount; ++materialIndex) { SetMaterial(materialIndex, this->MeshTemplate->GetMaterial(materialIndex)); } } } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::backupOriginalVertices() { int32 meshSectionCount = GetNumSections(); this->originalVertices.Reset(); this->originalVertices.Reserve(meshSectionCount); for(int32 index = 0; index < meshSectionCount; ++index) { const FProcMeshSection §ion = *GetProcMeshSection(index); this->originalVertices.Add(section.ProcVertexBuffer); } } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::calculateMeshMetrics() { FBox2D outerBounds; bool firstVertex = true; // First, figure out the outer bounds (min and max positions) or all vertices so // that we can determine where the dialog box' center lies. int32 sectionCount = this->originalVertices.Num(); for(int32 index = 0; index < sectionCount; ++index) { const TArray §ionVertices = this->originalVertices[index]; int32 vertexCount = sectionVertices.Num(); for(int32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { float x = sectionVertices[vertexIndex].Position.Y; float y = sectionVertices[vertexIndex].Position.Z; if(firstVertex) { outerBounds.Min.X = outerBounds.Max.X = x; outerBounds.Min.Y = outerBounds.Max.Y = y; firstVertex = false; } else { if(x < outerBounds.Min.X) { outerBounds.Min.X = x; } if(y < outerBounds.Min.Y) { outerBounds.Min.Y = y; } if(x > outerBounds.Max.X) { outerBounds.Max.X = x; } if(y > outerBounds.Max.Y) { outerBounds.Max.Y = y; } } } } // Calculate the center of the dialog box so that we can assign // its vertices into four quadrant s this->center = FVector2D( (outerBounds.Min.X + outerBounds.Max.X) / 2.0f, (outerBounds.Min.Y + outerBounds.Max.Y) / 2.0f ); // Confused? These are the inner bounds of each quadrant, so we start // out with the positions guaranteed to be furthest from the center this->innerBounds = outerBounds; // Now find the innermost vertex of each quadrant. for(int32 index = 0; index < sectionCount; ++index) { const TArray §ionVertices = this->originalVertices[index]; int32 vertexCount = sectionVertices.Num(); for(int32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { // Is X on the left half or right half of the dialog box? float x = sectionVertices[vertexIndex].Position.Y; if(x < center.X) { if(x > this->innerBounds.Min.X) { this->innerBounds.Min.X = x; } } else { if(x < this->innerBounds.Max.X) { this->innerBounds.Max.X = x; } } // Is Y on the bottom half or top half of the dialog box? float y = sectionVertices[vertexIndex].Position.Z; if(y < center.Y) { if(y > this->innerBounds.Min.Y) { this->innerBounds.Min.Y = y; } } else { if(y < this->innerBounds.Max.Y) { this->innerBounds.Max.Y = y; } } } } } // --------------------------------------------------------------------------------------------- // void UResizableDialogMeshComponent::forceVertexBufferUpdate() { // This method calls UpdateMeshSection() with the data it already has, // causing a needless copy-forth-and-back of all vertex data. It's necessary // because Epic didn't expose a simple MarkVertexDataDirty() method on its // procedural mesh class and this is the only way to actually update // the vertex buffer and not recreate it from scratch. int32 meshSectionCount = GetNumSections(); for(int32 index = 0; index < meshSectionCount; ++index) { FProcMeshSection §ion = *GetProcMeshSection(index); TArray &vertexBuffer = section.ProcVertexBuffer; int32 vertexCount = vertexBuffer.Num(); this->vertices.Reserve(vertexCount); this->normals.Reserve(vertexCount); this->uvs.Reserve(vertexCount); this->colors.Reserve(vertexCount); this->tangents.Reserve(vertexCount); // First, we'll copy the vertex buffer we already updated and which already // is in the format preferred by the UProceduralMeshComponent out into our // own separate arrays. for(int32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { this->vertices.Add(vertexBuffer[vertexIndex].Position); this->normals.Add(vertexBuffer[vertexIndex].Normal); this->uvs.Add(vertexBuffer[vertexIndex].UV0); this->colors.Add(vertexBuffer[vertexIndex].Color); this->tangents.Add(vertexBuffer[vertexIndex].Tangent); } // We then call UProceduralMeshComponent::UpdateMeshSection(), which will copy // our separate arrays back into the vertex buffer, filling it with the same // data it already contained in the first place. UpdateMeshSection_LinearColor( index, this->vertices, this->normals, this->uvs, this->colors, this->tangents ); this->vertices.Reset(); this->normals.Reset(); this->uvs.Reset(); this->colors.Reset(); this->tangents.Reset(); } } // --------------------------------------------------------------------------------------------- //