Files
UnrealEngineUWP/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp
JeanMichel Dignard e6d45383d5 Copying //UE4/Dev-Enterprise to //UE4/Dev-Main (Source: //UE4/Dev-Enterprise @ 4341740)
#lockdown Nick.Penwarden
#rb none

============================
  MAJOR FEATURES & CHANGES
============================

Change 4280523 by Patrick.Boutot

	Add option in AjaCustomTimeStep to wait until the frame to be ready. Previously, the frame was there but not yet processed so it was possible that it was not ready by the time we wanted to read it. It won't work with interlaced because the 2 fields are processed at the same time. In interlaced, will get a 30fps behaviour when we actually want a 60fps.
	Fix bug that didn't set and reset bIsOwned properly when it was first initialized as not owned.

Change 4280526 by Patrick.Boutot

	Add accessor to get the leaf media source or output.

Change 4280624 by Patrick.Boutot

	Add timecode acessor to media samples

Change 4280626 by Patrick.Boutot

	Rework the timing for AJA Media Player. Previously, we took the timing of the frame. That was a bad idea because if 2 incomings video frames were coming a the same time, you would only show one. Making the buffering system useless.
	That affects the Custom Time Step since it was waiting for the interrupt signal and in some behavior we would like the frame to be ready to be used by UE. Same the timecode in the MediaSample because we may not used it to stamps the frame.

Change 4283022 by Patrick.Boutot

	[EditorScriptingUtilitites] Check folder names invalid characters separatly from the object's name.
	#jira UE-59886,  UE-62333

Change 4283112 by Patrick.Boutot

	Remove MediaFrameworkUtilititesModule dependency to the Settings module at runtime.
	Rename TimemanagemenetEditor module names.

Change 4283426 by JeanLuc.Corenthin

	Fix crash with FBX file

	#jira UE-62501

Change 4284940 by Patrick.Boutot

	A widget that let you select a single permutation from a list. It groups the values into categories and removes duplicates inside that category.

Change 4285471 by Patrick.Boutot

	Remove MediaFrameworkUtilititesModule dependency to the Settings module at runtime.

Change 4286925 by Patrick.Boutot

	[AJA] Add support to read LTC from the reference In.
	Add more detail on video format and the device.
	MediaSource use the Permutations Selection widget to select his mode and device.
	Remove debugging option to trigger an AJA custom time step and timecode provider.
	Remove the UYVY pixel option from AJA. It's better do to the conversion on the AJA card that on the GPU.
	Change the tooltip and category for some AjaMediaSource properties.

Change 4287026 by Julien.StJean

	Modifed the file STimeCodeProviderTab.cpp to fix the position of a SComboButton that wasn't properly place.

Change 4287663 by Jon.Nabozny

	Add timecode messages into nDisplay, and sync those between Master and Slave

Change 4287884 by Jon.Nabozny

	Create a TimecodeProvider for SystemTime and introduce a notion for DefaultTimecodeProvider in Engine.

Change 4288050 by Jon.Nabozny

	Rework the TimeSynchronization implementation for usability and functionality.

Change 4288283 by Jon.Nabozny

	Fixed swapped MetaClass and DisplayName options on UEngine::DefaultTimecodeProviderClassName;

Change 4288352 by Jon.Nabozny

	Set TimecodeProviderClassName and DefaultTimecodeProviderClassName in BaseEngine.ini

Change 4288378 by Jon.Nabozny

	Fixup some issues in TimecodeSynchronizer where code was reset improperly due to multiple unshelves / resolves.

Change 4288394 by Jon.Nabozny

	Add TimeSync functionality into LiveLink. Also add test cases for this. This should allow us to easily synchronize multiple LiveLink sources together, as well as synchronize those to anything else using the sync system (Relies on CL-4235417)

Change 4288899 by Patrick.Boutot

	Fix initialization order of FMediaIOCorePlayerBase variables

Change 4289157 by Patrick.Boutot

	Allow the user to change the source of a capture without stopping the current capture.
	[AJA] AjaMediaCapture, add support for UpdateSceneViewport & UpdateRenderTarget
	@made by julien.stjean

Change 4291328 by Jon.Nabozny

	Report the Skeleton Guid with TimeSyncData and track sync state in LiveLinkTimeSynchronizationSource.
	This prevents a crash that can happen if a source is quickly cleared and reset before the next tick of Time Synchronization.

Change 4296294 by Jon.Nabozny

	Fixup errors when TimecodeProviderClassName is empty. It's valid to leave this empty.

Change 4297122 by Patrick.Boutot

	Media Profile with timecode provider & custom time step

Change 4301855 by Austin.Crismore

	Fix for movment scaling and virtual joystick controls. Movement scaling in for truck and dolly is locked to the world xy plane, and virtual joysticks use their own method for movement scaling now.

	#jira UE-61762, UE-62187

Change 4301856 by Austin.Crismore

	Virtual sequence level controller now listens to on object spawned, so that it can intercept the camera actor and disable attatching to HMD to prevent camera movement that isn't from the level sequence

	#jira UE-61766

Change 4301860 by Austin.Crismore

	Fix for touch scrubbing. Added default values back in. Added logic to only allow scrubbing when touch focus was off.

	#jira UE-61865

Change 4302294 by Jamie.Dale

	Added functions to get your the localized spoken and subtitle text from a dialogue wave

Change 4304393 by Jamie.Dale

	Added support for BlueprintAssignable properties in Python

Change 4305852 by Jamie.Dale

	Removed hard-dependency between EditorScriptingUtilities and PythonScriptPlugin

	Backed-out changelist 4259264 and query Python availability based on whether anything is available to handle the command

	#jira UE-62318

Change 4308550 by Jamie.Dale

	Fixed crash when passing a null world to Python actor iterators

Change 4311867 by Homam.Bahnassi

	Revit master material with exposed parameters matching the API when possible.

Change 4314428 by Francis.Hurteau

	Made the usage of the bBuildDeveloperTools switch independent of the bCompileAgainstEngine switch.
	Changed bBuildDeveloperTools TargetRule in UnrealBuildTool to a nullable to keep the old behavior in case where bBuildDeveloperTools wasn't explicitly set in TargetRules

Change 4315134 by Jamie.Dale

	Defer editable text focus selection until mouse-up to allow the user to make an initial selection

	#jira UE-58086

Change 4318615 by Johan.Duparc

	EditorFactories: consistent return values after asset import.

Change 4322459 by Jamie.Dale

	Made SequencerScripting an Editor plugin as it depends on PythonScriptPlugin which is an Editor plugin

	This was causing issues at runtime when SequencerScripting was enabled, as it failed to load PythonScriptPlugin (which hadn't been built).

Change 4323341 by Francis.Hurteau

	Implement proper message bus protocol version negociation with static nodes

Change 4323733 by Francis.Hurteau

	Fix VR Pausing Sequence Scrubbing just setting playback speed to 0.0

Change 4324319 by Jamie.Dale

	Exposed transactions to Blueprints

Change 4325847 by Alistair.White

	Copying //Tasks/UE4/Private-PixelStreaming@4325566 to Dev-Enterprise-Minimal (//UE4/Dev-Enterprise-Minimal)

	This adds the new experimental PixelStreaming plugin to allow streaming of an Unreal client's audio & video stream to a browser through the WebRTC protocol to support new uses for enterprise customers.

Change 4326282 by Simon.Tourangeau

	nDisplay native present handler

Change 4326581 by Jamie.Dale

	Replacing FDateTime with int64 Ticks value to workaround UE-63485

Change 4326599 by Homam.Bahnassi

	Moving texture coords outside UVEdit function to allow using different UV channels.

Change 4333250 by Francis.Hurteau

	Small TFuture changes:
	* cleans up TFuture::Then with usage of TUniqueFunction
	* added TFuture::Reset to invalidate it and remove continuation from a future shared state

Change 4333359 by Homam.Bahnassi

	Support scaling and rotating UVs around arbitrary pivot

Change 4333566 by Johan.Duparc

	Expose ProxyLOD functionalities to Scripting
	#jira UEENT-1788

Change 4333988 by Jamie.Dale

	Allow UHT to parse FText default parameter values

	INVTEXT, NSLOCTEXT, LOCTABLE, and FText::GetEmpty() are supported. LOCTEXT isn't as it relies on an external macro that is known to C++ but not to UHT (NSLOCTEXT can easily be used instead).

Change 4335020 by Francis.Hurteau

	Uncomment MessageBus::Send deprecation notice for 4.21
	Update MessageBus Send usage to new API

Change 4335195 by JeanMichel.Dignard

	Add a SetLodFromStaticMesh script utility function

	#jira UEENT-1789

Change 4335231 by Anousack.Kitisa

	Added functions to generate planar, cylindrical, box UV mapping.

	#jira UEENT-1598

Change 4335373 by Jamie.Dale

	Cleaned up some places creating empty literal texts

Change 4335458 by Jamie.Dale

	Allow UHT to parse FText() as an alias of FText::GetEmpty() when processing default values

Change 4335875 by Max.Chen

	Sequencer: Clear RF_Transient on pasted tracks/sections

	#jira UE-63537

Change 4336497 by Johan.Duparc

	ProxyLOD: Fix progress bar issue
	- removed duplicated code
	- removed duplicated LongTask object
	#jira UEENT-1788

Change 4336723 by Jamie.Dale

	Ensure that Python generated types create their CDO at the correct point

	#jira UE-62895

Change 4340594 by Ben.Marsh

	Fix manifest being invalidated when building two enterprise targets in a row. Fixes CIS error.

	#jira UE-63644

[CL 4342443 by JeanMichel Dignard in Main branch]
2018-09-04 16:35:02 -04:00

1506 lines
60 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "MeshDescriptionOperations.h"
#include "UObject/Package.h"
#include "MeshDescription.h"
#include "MeshAttributes.h"
#include "RawMesh.h"
#include "LayoutUV.h"
#include "OverlappingCorners.h"
#include "RenderUtils.h"
#include "mikktspace.h"
#include "UVMapSettings.h"
DEFINE_LOG_CATEGORY(LogMeshDescriptionOperations);
#define LOCTEXT_NAMESPACE "MeshDescriptionOperations"
//////////////////////////////////////////////////////////////////////////
// Local structure
struct FVertexInfo
{
FVertexInfo()
{
PolygonID = FPolygonID::Invalid;
VertexInstanceID = FVertexInstanceID::Invalid;
UVs = FVector2D(0.0f, 0.0f);
EdgeIDs.Reserve(2);//Most of the time a edge has two triangles
}
FPolygonID PolygonID;
FVertexInstanceID VertexInstanceID;
FVector2D UVs;
TArray<FEdgeID> EdgeIDs;
};
/** Helper struct for building acceleration structures. */
namespace MeshDescriptionOperationNamespace
{
struct FIndexAndZ
{
float Z;
int32 Index;
const FVector *OriginalVector;
/** Default constructor. */
FIndexAndZ() {}
/** Initialization constructor. */
FIndexAndZ(int32 InIndex, const FVector& V)
{
Z = 0.30f * V.X + 0.33f * V.Y + 0.37f * V.Z;
Index = InIndex;
OriginalVector = &V;
}
};
/** Sorting function for vertex Z/index pairs. */
struct FCompareIndexAndZ
{
FORCEINLINE bool operator()(FIndexAndZ const& A, FIndexAndZ const& B) const { return A.Z < B.Z; }
};
}
//////////////////////////////////////////////////////////////////////////
// Converters
void FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(const FMeshDescription& SourceMeshDescription, FRawMesh& DestinationRawMesh)
{
TMap<FPolygonID, uint32> PolygonSmoothGroup;
PolygonSmoothGroup.Reserve(SourceMeshDescription.Polygons().GetArraySize());
TArray<bool> ConsumedPolygons;
ConsumedPolygons.AddZeroed(SourceMeshDescription.Polygons().GetArraySize());
TMap < FPolygonID, uint32> PolygonAvoidances;
TEdgeAttributesConstRef<bool> EdgeHardnesses = SourceMeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
if (ConsumedPolygons[PolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> ConnectedPolygons;
TArray<FPolygonID> LastConnectedPolygons;
ConnectedPolygons.Add(PolygonID);
LastConnectedPolygons.Add(FPolygonID::Invalid);
while (ConnectedPolygons.Num() > 0)
{
check(LastConnectedPolygons.Num() == ConnectedPolygons.Num());
FPolygonID LastPolygonID = LastConnectedPolygons.Pop(true);
FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(true);
if (ConsumedPolygons[CurrentPolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> SoftEdgeNeigbors;
uint32& SmoothGroup = PolygonSmoothGroup.FindOrAdd(CurrentPolygonID);
uint32 AvoidSmoothGroup = 0;
uint32 NeighborSmoothGroup = 0;
const uint32 LastSmoothGroupValue = (LastPolygonID == FPolygonID::Invalid) ? 0 : PolygonSmoothGroup[LastPolygonID];
TArray<FEdgeID> PolygonEdges;
SourceMeshDescription.GetPolygonEdges(CurrentPolygonID, PolygonEdges);
for (const FEdgeID& EdgeID : PolygonEdges)
{
bool bIsHardEdge = EdgeHardnesses[EdgeID];
const TArray<FPolygonID>& EdgeConnectedPolygons = SourceMeshDescription.GetEdgeConnectedPolygons(EdgeID);
for (const FPolygonID& EdgePolygonID : EdgeConnectedPolygons)
{
if (EdgePolygonID == CurrentPolygonID)
{
continue;
}
uint32 SmoothValue = 0;
if (PolygonSmoothGroup.Contains(EdgePolygonID))
{
SmoothValue = PolygonSmoothGroup[EdgePolygonID];
}
if (bIsHardEdge) //Hard Edge
{
AvoidSmoothGroup |= SmoothValue;
}
else
{
NeighborSmoothGroup |= SmoothValue;
//Put all none hard edge polygon in the next iteration
if (!ConsumedPolygons[EdgePolygonID.GetValue()])
{
ConnectedPolygons.Add(EdgePolygonID);
LastConnectedPolygons.Add(CurrentPolygonID);
}
else
{
SoftEdgeNeigbors.Add(EdgePolygonID);
}
}
}
}
if (AvoidSmoothGroup != 0)
{
PolygonAvoidances.FindOrAdd(CurrentPolygonID) = AvoidSmoothGroup;
//find neighbor avoidance
for (FPolygonID& NeighborID : SoftEdgeNeigbors)
{
if (!PolygonAvoidances.Contains(NeighborID))
{
continue;
}
AvoidSmoothGroup |= PolygonAvoidances[NeighborID];
}
uint32 NewSmoothGroup = 1;
while ((NewSmoothGroup & AvoidSmoothGroup) != 0 && NewSmoothGroup < MAX_uint32)
{
//Shift the smooth group
NewSmoothGroup = NewSmoothGroup << 1;
}
SmoothGroup = NewSmoothGroup;
//Apply to all neighboard
for (FPolygonID& NeighborID : SoftEdgeNeigbors)
{
PolygonSmoothGroup[NeighborID] |= NewSmoothGroup;
}
}
else if (NeighborSmoothGroup != 0)
{
SmoothGroup |= LastSmoothGroupValue | NeighborSmoothGroup;
}
else
{
SmoothGroup = 1;
}
ConsumedPolygons[CurrentPolygonID.GetValue()] = true;
}
}
//Now we have to put the data into the RawMesh
int32 TriangleIndex = 0;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
uint32 PolygonSmoothValue = PolygonSmoothGroup[PolygonID];
const TArray<FMeshTriangle>& Triangles = SourceMeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle& MeshTriangle : Triangles)
{
DestinationRawMesh.FaceSmoothingMasks[TriangleIndex++] = PolygonSmoothValue;
}
}
}
void FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(const TArray<uint32>& FaceSmoothingMasks, FMeshDescription& DestinationMeshDescription)
{
TEdgeAttributesRef<bool> EdgeHardnesses = DestinationMeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TArray<bool> ConsumedPolygons;
ConsumedPolygons.AddZeroed(DestinationMeshDescription.Polygons().Num());
for (const FPolygonID PolygonID : DestinationMeshDescription.Polygons().GetElementIDs())
{
if (ConsumedPolygons[PolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> ConnectedPolygons;
ConnectedPolygons.Add(PolygonID);
while (ConnectedPolygons.Num() > 0)
{
FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(true);
int32 CurrentPolygonIDValue = CurrentPolygonID.GetValue();
check(FaceSmoothingMasks.IsValidIndex(CurrentPolygonIDValue));
const uint32 ReferenceSmoothGroup = FaceSmoothingMasks[CurrentPolygonIDValue];
TArray<FEdgeID> PolygonEdges;
DestinationMeshDescription.GetPolygonEdges(CurrentPolygonID, PolygonEdges);
for (const FEdgeID& EdgeID : PolygonEdges)
{
const bool bIsHardEdge = EdgeHardnesses[EdgeID];
if (bIsHardEdge)
{
continue;
}
const TArray<FPolygonID>& EdgeConnectedPolygons = DestinationMeshDescription.GetEdgeConnectedPolygons(EdgeID);
for (const FPolygonID& EdgePolygonID : EdgeConnectedPolygons)
{
int32 EdgePolygonIDValue = EdgePolygonID.GetValue();
if (EdgePolygonID == CurrentPolygonID || ConsumedPolygons[EdgePolygonIDValue])
{
continue;
}
check(FaceSmoothingMasks.IsValidIndex(EdgePolygonIDValue));
const uint32 TestSmoothGroup = FaceSmoothingMasks[EdgePolygonIDValue];
if ((TestSmoothGroup & ReferenceSmoothGroup) == 0)
{
EdgeHardnesses[EdgeID] = true;
break;
}
else
{
ConnectedPolygons.Add(EdgePolygonID);
}
}
}
ConsumedPolygons[CurrentPolygonID.GetValue()] = true;
}
}
}
void FMeshDescriptionOperations::ConvertToRawMesh(const FMeshDescription& SourceMeshDescription, FRawMesh& DestinationRawMesh, const TMap<FName, int32>& MaterialMap)
{
DestinationRawMesh.Empty();
//Gather all array data
TVertexAttributesConstRef<FVector> VertexPositions = SourceMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesConstRef<FVector> VertexInstanceNormals = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesConstRef<FVector> VertexInstanceTangents = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesConstRef<FVector4> VertexInstanceColors = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = SourceMeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
DestinationRawMesh.VertexPositions.AddZeroed(SourceMeshDescription.Vertices().Num());
TArray<int32> RemapVerts;
RemapVerts.AddZeroed(SourceMeshDescription.Vertices().GetArraySize());
int32 VertexIndex = 0;
for (const FVertexID& VertexID : SourceMeshDescription.Vertices().GetElementIDs())
{
DestinationRawMesh.VertexPositions[VertexIndex] = VertexPositions[VertexID];
RemapVerts[VertexID.GetValue()] = VertexIndex;
++VertexIndex;
}
int32 TriangleNumber = 0;
for (const FPolygonID& PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
TriangleNumber += SourceMeshDescription.GetPolygonTriangles(PolygonID).Num();
}
DestinationRawMesh.FaceMaterialIndices.AddZeroed(TriangleNumber);
DestinationRawMesh.FaceSmoothingMasks.AddZeroed(TriangleNumber);
int32 WedgeIndexNumber = TriangleNumber * 3;
DestinationRawMesh.WedgeColors.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeIndices.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentX.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentY.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentZ.AddZeroed(WedgeIndexNumber);
int32 ExistingUVCount = VertexInstanceUVs.GetNumIndices();
for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex)
{
DestinationRawMesh.WedgeTexCoords[UVIndex].AddZeroed(WedgeIndexNumber);
}
int32 TriangleIndex = 0;
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
const FPolygonGroupID& PolygonGroupID = SourceMeshDescription.GetPolygonPolygonGroup(PolygonID);
int32 PolygonIDValue = PolygonID.GetValue();
const TArray<FMeshTriangle>& Triangles = SourceMeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle& MeshTriangle : Triangles)
{
if (MaterialMap.Num() > 0 && MaterialMap.Contains(PolygonGroupMaterialSlotName[PolygonGroupID]))
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = MaterialMap[PolygonGroupMaterialSlotName[PolygonGroupID]];
}
else
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = 0;
}
DestinationRawMesh.FaceSmoothingMasks[TriangleIndex] = 0; //Conversion of soft/hard to smooth mask is done after the geometry is converted
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(Corner);
DestinationRawMesh.WedgeColors[WedgeIndex] = FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true);
DestinationRawMesh.WedgeIndices[WedgeIndex] = RemapVerts[SourceMeshDescription.GetVertexInstanceVertex(VertexInstanceID).GetValue()];
DestinationRawMesh.WedgeTangentX[WedgeIndex] = VertexInstanceTangents[VertexInstanceID];
DestinationRawMesh.WedgeTangentY[WedgeIndex] = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
DestinationRawMesh.WedgeTangentZ[WedgeIndex] = VertexInstanceNormals[VertexInstanceID];
for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex)
{
DestinationRawMesh.WedgeTexCoords[UVIndex][WedgeIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
}
++WedgeIndex;
}
++TriangleIndex;
}
}
//Convert the smoothgroup
ConvertHardEdgesToSmoothGroup(SourceMeshDescription, DestinationRawMesh);
}
//We want to fill the FMeshDescription vertex position mesh attribute with the FRawMesh vertex position
//We will also weld the vertex position (old FRawMesh is not always welded) and construct a mapping array to match the FVertexID
void FillMeshDescriptionVertexPositionNoDuplicate(const TArray<FVector>& RawMeshVertexPositions, FMeshDescription& DestinationMeshDescription, TArray<FVertexID>& RemapVertexPosition)
{
TVertexAttributesRef<FVector> VertexPositions = DestinationMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
const int32 NumVertex = RawMeshVertexPositions.Num();
TMap<int32, int32> TempRemapVertexPosition;
TempRemapVertexPosition.Reserve(NumVertex);
// Create a list of vertex Z/index pairs
TArray<MeshDescriptionOperationNamespace::FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumVertex);
for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex)
{
new(VertIndexAndZ)MeshDescriptionOperationNamespace::FIndexAndZ(VertexIndex, RawMeshVertexPositions[VertexIndex]);
}
// Sort the vertices by z value
VertIndexAndZ.Sort(MeshDescriptionOperationNamespace::FCompareIndexAndZ());
int32 VertexCount = 0;
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
int32 Index_i = VertIndexAndZ[i].Index;
if (TempRemapVertexPosition.Contains(Index_i))
{
continue;
}
TempRemapVertexPosition.FindOrAdd(Index_i) = VertexCount;
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > SMALL_NUMBER)
break; // can't be any more dups
const FVector& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector& PositionB = *(VertIndexAndZ[j].OriginalVector);
if (PositionA.Equals(PositionB, SMALL_NUMBER))
{
TempRemapVertexPosition.FindOrAdd(VertIndexAndZ[j].Index) = VertexCount;
}
}
VertexCount++;
}
//Make sure the vertex are added in the same order to be lossless when converting the FRawMesh
//In case there is a duplicate even reordering it will not be lossless, but MeshDescription do not support
//bad data like duplicated vertex position.
RemapVertexPosition.AddUninitialized(NumVertex);
DestinationMeshDescription.ReserveNewVertices(VertexCount);
TArray<FVertexID> UniqueVertexDone;
UniqueVertexDone.AddUninitialized(VertexCount);
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
{
UniqueVertexDone[VertexIndex] = FVertexID::Invalid;
}
for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex)
{
int32 RealIndex = TempRemapVertexPosition[VertexIndex];
if (UniqueVertexDone[RealIndex] != FVertexID::Invalid)
{
RemapVertexPosition[VertexIndex] = UniqueVertexDone[RealIndex];
continue;
}
FVertexID VertexID = DestinationMeshDescription.CreateVertex();
UniqueVertexDone[RealIndex] = VertexID;
VertexPositions[VertexID] = RawMeshVertexPositions[VertexIndex];
RemapVertexPosition[VertexIndex] = VertexID;
}
}
//Discover degenerated triangle
bool IsTriangleDegenerated(const FRawMesh& SourceRawMesh, const TArray<FVertexID>& RemapVertexPosition, const int32 VerticeIndexBase)
{
FVertexID VertexIDs[3];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 VerticeIndex = VerticeIndexBase + Corner;
VertexIDs[Corner] = RemapVertexPosition[SourceRawMesh.WedgeIndices[VerticeIndex]];
}
return (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]);
}
void FMeshDescriptionOperations::ConvertFromRawMesh(const FRawMesh& SourceRawMesh, FMeshDescription& DestinationMeshDescription, const TMap<int32, FName>& MaterialMap)
{
DestinationMeshDescription.Empty();
DestinationMeshDescription.ReserveNewVertexInstances(SourceRawMesh.WedgeIndices.Num());
DestinationMeshDescription.ReserveNewPolygons(SourceRawMesh.WedgeIndices.Num() / 3);
//Approximately 2.5 edges per polygons
DestinationMeshDescription.ReserveNewEdges(SourceRawMesh.WedgeIndices.Num() * 2.5f / 3);
//Gather all array data
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = DestinationMeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
int32 NumTexCoords = 0;
int32 MaxTexCoords = MAX_MESH_TEXTURE_COORDS;
TArray<int32> TextureCoordinnateRemapIndex;
TextureCoordinnateRemapIndex.AddZeroed(MaxTexCoords);
for (int32 TextureCoordinnateIndex = 0; TextureCoordinnateIndex < MaxTexCoords; ++TextureCoordinnateIndex)
{
TextureCoordinnateRemapIndex[TextureCoordinnateIndex] = INDEX_NONE;
if (SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex].Num() == SourceRawMesh.WedgeIndices.Num())
{
TextureCoordinnateRemapIndex[TextureCoordinnateIndex] = NumTexCoords;
NumTexCoords++;
}
}
VertexInstanceUVs.SetNumIndices(NumTexCoords);
//Ensure we do not have any duplicate, We found all duplicated vertex and compact them and build a remap indice array to remap the wedgeindices
TArray<FVertexID> RemapVertexPosition;
FillMeshDescriptionVertexPositionNoDuplicate(SourceRawMesh.VertexPositions, DestinationMeshDescription, RemapVertexPosition);
bool bHasColors = SourceRawMesh.WedgeColors.Num() > 0;
bool bHasTangents = SourceRawMesh.WedgeTangentX.Num() > 0 && SourceRawMesh.WedgeTangentY.Num() > 0;
bool bHasNormals = SourceRawMesh.WedgeTangentZ.Num() > 0;
TArray<FPolygonGroupID> PolygonGroups;
TMap<int32, FPolygonGroupID> MaterialIndexToPolygonGroup;
//Create the PolygonGroups
for(int32 MaterialIndex : SourceRawMesh.FaceMaterialIndices)
{
if (!MaterialIndexToPolygonGroup.Contains(MaterialIndex))
{
FPolygonGroupID PolygonGroupID(MaterialIndex);
DestinationMeshDescription.CreatePolygonGroupWithID(PolygonGroupID);
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));
if (MaterialMap.Contains(MaterialIndex))
{
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = MaterialMap[MaterialIndex];
}
PolygonGroups.Add(PolygonGroupID);
MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
}
}
//Triangles
int32 TriangleCount = SourceRawMesh.WedgeIndices.Num() / 3;
for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
int32 VerticeIndexBase = TriangleIndex * 3;
//Check if the triangle is degenerated and skip the data if its the case
if (IsTriangleDegenerated(SourceRawMesh, RemapVertexPosition, VerticeIndexBase))
{
continue;
}
//PolygonGroup
FPolygonGroupID PolygonGroupID = FPolygonGroupID::Invalid;
FName PolygonGroupImportedMaterialSlotName = NAME_None;
int32 MaterialIndex = SourceRawMesh.FaceMaterialIndices[TriangleIndex];
if (MaterialIndexToPolygonGroup.Contains(MaterialIndex))
{
PolygonGroupID = MaterialIndexToPolygonGroup[MaterialIndex];
}
else if (MaterialMap.Num() > 0 && MaterialMap.Contains(MaterialIndex))
{
PolygonGroupImportedMaterialSlotName = MaterialMap[MaterialIndex];
for (const FPolygonGroupID& SearchPolygonGroupID : DestinationMeshDescription.PolygonGroups().GetElementIDs())
{
if (PolygonGroupImportedMaterialSlotNames[SearchPolygonGroupID] == PolygonGroupImportedMaterialSlotName)
{
PolygonGroupID = SearchPolygonGroupID;
break;
}
}
}
if (PolygonGroupID == FPolygonGroupID::Invalid)
{
PolygonGroupID = DestinationMeshDescription.CreatePolygonGroup();
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = PolygonGroupImportedMaterialSlotName == NAME_None ? FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex)) : PolygonGroupImportedMaterialSlotName;
PolygonGroups.Add(PolygonGroupID);
MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
}
FVertexInstanceID TriangleVertexInstanceIDs[3];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 VerticeIndex = VerticeIndexBase + Corner;
FVertexID VertexID = RemapVertexPosition[SourceRawMesh.WedgeIndices[VerticeIndex]];
FVertexInstanceID VertexInstanceID = DestinationMeshDescription.CreateVertexInstance(VertexID);
TriangleVertexInstanceIDs[Corner] = VertexInstanceID;
VertexInstanceColors[VertexInstanceID] = bHasColors ? FLinearColor::FromSRGBColor(SourceRawMesh.WedgeColors[VerticeIndex]) : FLinearColor::White;
VertexInstanceTangents[VertexInstanceID] = bHasTangents ? SourceRawMesh.WedgeTangentX[VerticeIndex] : FVector(ForceInitToZero);
VertexInstanceBinormalSigns[VertexInstanceID] = bHasTangents ? GetBasisDeterminantSign(SourceRawMesh.WedgeTangentX[VerticeIndex].GetSafeNormal(), SourceRawMesh.WedgeTangentY[VerticeIndex].GetSafeNormal(), SourceRawMesh.WedgeTangentZ[VerticeIndex].GetSafeNormal()) : 0.0f;
VertexInstanceNormals[VertexInstanceID] = bHasNormals ? SourceRawMesh.WedgeTangentZ[VerticeIndex] : FVector(ForceInitToZero);
for (int32 TextureCoordinnateIndex = 0; TextureCoordinnateIndex < NumTexCoords; ++TextureCoordinnateIndex)
{
int32 TextureCoordIndex = TextureCoordinnateRemapIndex[TextureCoordinnateIndex];
if (TextureCoordIndex == INDEX_NONE)
{
continue;
}
VertexInstanceUVs.Set(VertexInstanceID, TextureCoordIndex, SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex][VerticeIndex]);
}
}
//Create the polygon edges
TArray<FMeshDescription::FContourPoint> Contours;
for (uint32 Corner = 0; Corner < 3; ++Corner)
{
int32 ContourPointIndex = Contours.AddDefaulted();
FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex];
//Find the matching edge ID
int32 CornerIndices[2];
CornerIndices[0] = (Corner + 0) % 3;
CornerIndices[1] = (Corner + 1) % 3;
FVertexID EdgeVertexIDs[2];
EdgeVertexIDs[0] = DestinationMeshDescription.GetVertexInstanceVertex(FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[0]]));
EdgeVertexIDs[1] = DestinationMeshDescription.GetVertexInstanceVertex(FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[1]]));
FEdgeID MatchEdgeId = DestinationMeshDescription.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
if (MatchEdgeId == FEdgeID::Invalid)
{
MatchEdgeId = DestinationMeshDescription.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
}
ContourPoint.EdgeID = MatchEdgeId;
ContourPoint.VertexInstanceID = FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[0]]);
}
const FPolygonID NewPolygonID = DestinationMeshDescription.CreatePolygon(PolygonGroupID, Contours);
int32 NewTriangleIndex = DestinationMeshDescription.GetPolygonTriangles(NewPolygonID).AddDefaulted();
FMeshTriangle& NewTriangle = DestinationMeshDescription.GetPolygonTriangles(NewPolygonID)[NewTriangleIndex];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
FVertexInstanceID VertexInstanceID = TriangleVertexInstanceIDs[Corner];
NewTriangle.SetVertexInstanceID(Corner, VertexInstanceID);
}
}
ConvertSmoothGroupToHardEdges(SourceRawMesh.FaceSmoothingMasks, DestinationMeshDescription);
//Create the missing normals and tangents, should we use Mikkt space for tangent???
if (!bHasNormals || !bHasTangents)
{
//DestinationMeshDescription.ComputePolygonTangentsAndNormals(0.0f);
FMeshDescriptionOperations::CreatePolygonNTB(DestinationMeshDescription, 0.0f);
//EComputeNTBsOptions ComputeNTBsOptions = (bHasNormals ? EComputeNTBsOptions::None : EComputeNTBsOptions::Normals) | (bHasTangents ? EComputeNTBsOptions::None : EComputeNTBsOptions::Tangents);
//DestinationMeshDescription.ComputeTangentsAndNormals(ComputeNTBsOptions);
//Create the missing normals and tangents
if (!bHasNormals)
{
CreateNormals(DestinationMeshDescription, ETangentOptions::BlendOverlappingNormals, false);
}
CreateMikktTangents(DestinationMeshDescription, ETangentOptions::BlendOverlappingNormals);
}
}
//////////////////////////////////////////////////////////////////////////
// Normals tangents and Bi-normals
void FMeshDescriptionOperations::CreatePolygonNTB(FMeshDescription& MeshDescription, float ComparisonThreshold)
{
const TVertexAttributesRef<FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesRef<FVector2D> VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonAttributesRef<FVector> PolygonNormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Normal);
TPolygonAttributesRef<FVector> PolygonTangents = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Tangent);
TPolygonAttributesRef<FVector> PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Binormal);
FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
FVertexArray& VertexArray = MeshDescription.Vertices();
FPolygonArray& PolygonArray = MeshDescription.Polygons();
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
if (!PolygonNormals[PolygonID].IsNearlyZero())
{
//By pass normal calculation if its already done
continue;
}
const TArray<FMeshTriangle>& MeshTriangles = MeshDescription.GetPolygonTriangles(PolygonID);
FVector TangentX(0.0f);
FVector TangentY(0.0f);
FVector TangentZ(0.0f);
for (const FMeshTriangle& MeshTriangle : MeshTriangles)
{
int32 UVIndex = 0;
FVector P[3];
FVector2D UVs[3];
for (int32 i = 0; i < 3; ++i)
{
const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(i);
UVs[i] = VertexUVs.Get(VertexInstanceID, 0); // UV0
P[i] = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)];
}
const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
//Check for degenerated polygons, avoid NAN
if (!Normal.IsNearlyZero(ComparisonThreshold))
{
FMatrix ParameterToLocal(
FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
FPlane(P[0].X, P[0].Y, P[0].Z, 0),
FPlane(0, 0, 0, 1)
);
FMatrix ParameterToTexture(
FPlane(UVs[1].X - UVs[0].X, UVs[1].Y - UVs[0].Y, 0, 0),
FPlane(UVs[2].X - UVs[0].X, UVs[2].Y - UVs[0].Y, 0, 0),
FPlane(UVs[0].X, UVs[0].Y, 1, 0),
FPlane(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
FVector TmpTangentX(0.0f);
FVector TmpTangentY(0.0f);
FVector TmpTangentZ(0.0f);
TmpTangentX = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal();
TmpTangentY = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal();
TmpTangentZ = Normal;
FVector::CreateOrthonormalBasis(TmpTangentX, TmpTangentY, TmpTangentZ);
TangentX += TmpTangentX;
TangentY += TmpTangentY;
TangentZ += TmpTangentZ;
}
else
{
//This will force a recompute of the normals and tangents
TangentX = FVector(0.0f);
TangentY = FVector(0.0f);
TangentZ = FVector(0.0f);
break;
}
}
TangentX.Normalize();
TangentY.Normalize();
TangentZ.Normalize();
PolygonTangents[PolygonID] = TangentX;
PolygonBinormals[PolygonID] = TangentY;
PolygonNormals[PolygonID] = TangentZ;
}
}
void FMeshDescriptionOperations::CreateNormals(FMeshDescription& MeshDescription, FMeshDescriptionOperations::ETangentOptions TangentOptions, bool bComputeTangent)
{
//For each vertex compute the normals for every connected edges that are smooth betwween hard edges
// H A B
// \ || /
// G -- ** -- C
// // | \
// F E D
//
// The double ** are the vertex, the double line are hard edges, the single line are soft edge.
// A and F are hard, all other edges are soft. The goal is to compute two average normals one from
// A to F and a second from F to A. Then we can set the vertex instance normals accordingly.
// First normal(A to F) = Normalize(A+B+C+D+E+F)
// Second normal(F to A) = Normalize(F+G+H+A)
// We found the connected edge using the triangle that share edges
// @todo: provide an option to weight each contributing polygon normal according to the size of
// the angle it makes with the vertex being calculated. This means that triangulated faces whose
// internal edge meets the vertex doesn't get undue extra weight.
const TVertexInstanceAttributesRef<FVector2D> VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TVertexInstanceAttributesRef<FVector> VertexNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TPolygonAttributesRef<FVector> PolygonNormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Normal);
TPolygonAttributesRef<FVector> PolygonTangents = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Tangent);
TPolygonAttributesRef<FVector> PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Binormal);
TMap<FPolygonID, FVertexInfo> VertexInfoMap;
VertexInfoMap.Reserve(20);
//Iterate all vertex to compute normals for all vertex instance
for (const FVertexID VertexID : MeshDescription.Vertices().GetElementIDs())
{
VertexInfoMap.Reset();
bool bPointHasAllTangents = true;
//Fill the VertexInfoMap
for (const FEdgeID EdgeID : MeshDescription.GetVertexConnectedEdges(VertexID))
{
for (const FPolygonID PolygonID : MeshDescription.GetEdgeConnectedPolygons(EdgeID))
{
FVertexInfo& VertexInfo = VertexInfoMap.FindOrAdd(PolygonID);
int32 EdgeIndex = VertexInfo.EdgeIDs.AddUnique(EdgeID);
if (VertexInfo.PolygonID == FPolygonID::Invalid)
{
VertexInfo.PolygonID = PolygonID;
for (const FVertexInstanceID VertexInstanceID : MeshDescription.GetPolygonPerimeterVertexInstances(PolygonID))
{
if (MeshDescription.GetVertexInstanceVertex(VertexInstanceID) == VertexID)
{
VertexInfo.VertexInstanceID = VertexInstanceID;
VertexInfo.UVs = VertexUVs.Get(VertexInstanceID, 0); // UV0
bPointHasAllTangents &= !VertexNormals[VertexInstanceID].IsNearlyZero() && !VertexTangents[VertexInstanceID].IsNearlyZero();
if (bPointHasAllTangents)
{
FVector TangentX = VertexTangents[VertexInstanceID].GetSafeNormal();
FVector TangentZ = VertexNormals[VertexInstanceID].GetSafeNormal();
FVector TangentY = (FVector::CrossProduct(TangentZ, TangentX).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID]).GetSafeNormal();
if (TangentX.ContainsNaN() || TangentX.IsNearlyZero(SMALL_NUMBER) ||
TangentY.ContainsNaN() || TangentY.IsNearlyZero(SMALL_NUMBER) ||
TangentZ.ContainsNaN() || TangentZ.IsNearlyZero(SMALL_NUMBER))
{
bPointHasAllTangents = false;
}
}
break;
}
}
}
}
}
if (bPointHasAllTangents)
{
continue;
}
//Build all group by recursively traverse all polygon connected to the vertex
TArray<TArray<FPolygonID>> Groups;
TArray<FPolygonID> ConsumedPolygon;
for (auto Kvp : VertexInfoMap)
{
if (ConsumedPolygon.Contains(Kvp.Key))
{
continue;
}
int32 CurrentGroupIndex = Groups.AddZeroed();
TArray<FPolygonID>& CurrentGroup = Groups[CurrentGroupIndex];
TArray<FPolygonID> PolygonQueue;
PolygonQueue.Add(Kvp.Key); //Use a queue to avoid recursive function
while (PolygonQueue.Num() > 0)
{
FPolygonID CurrentPolygonID = PolygonQueue.Pop(true);
FVertexInfo& CurrentVertexInfo = VertexInfoMap.FindOrAdd(CurrentPolygonID);
CurrentGroup.AddUnique(CurrentVertexInfo.PolygonID);
ConsumedPolygon.AddUnique(CurrentVertexInfo.PolygonID);
const TEdgeAttributesRef<bool> EdgeHardnesses = MeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
for (const FEdgeID EdgeID : CurrentVertexInfo.EdgeIDs)
{
if (EdgeHardnesses[EdgeID])
{
//End of the group
continue;
}
for (const FPolygonID PolygonID : MeshDescription.GetEdgeConnectedPolygons(EdgeID))
{
if (PolygonID == CurrentVertexInfo.PolygonID)
{
continue;
}
//Add this polygon to the group
FVertexInfo& OtherVertexInfo = VertexInfoMap.FindOrAdd(PolygonID);
//Do not repeat polygons
if (!ConsumedPolygon.Contains(OtherVertexInfo.PolygonID))
{
PolygonQueue.Add(PolygonID);
}
}
}
}
}
//Smooth every connected group
ConsumedPolygon.Reset();
for (const TArray<FPolygonID>& Group : Groups)
{
//Compute tangents data
TMap<FVector2D, FVector> GroupTangent;
TMap<FVector2D, FVector> GroupBiNormal;
TArray<FVertexInstanceID> VertexInstanceInGroup;
FVector GroupNormal(0.0f);
for (const FPolygonID PolygonID : Group)
{
FVector PolyNormal = PolygonNormals[PolygonID];
FVector PolyTangent = PolygonTangents[PolygonID];
FVector PolyBinormal = PolygonBinormals[PolygonID];
ConsumedPolygon.Add(PolygonID);
VertexInstanceInGroup.Add(VertexInfoMap[PolygonID].VertexInstanceID);
if (!PolyNormal.IsNearlyZero(SMALL_NUMBER) && !PolyNormal.ContainsNaN())
{
GroupNormal += PolyNormal;
}
if (bComputeTangent)
{
const FVector2D UVs = VertexInfoMap[PolygonID].UVs;
bool CreateGroup = (!GroupTangent.Contains(UVs));
FVector& GroupTangentValue = GroupTangent.FindOrAdd(UVs);
FVector& GroupBiNormalValue = GroupBiNormal.FindOrAdd(UVs);
if (CreateGroup)
{
GroupTangentValue = FVector(0.0f);
GroupBiNormalValue = FVector(0.0f);
}
if (!PolyTangent.IsNearlyZero(SMALL_NUMBER) && !PolyTangent.ContainsNaN())
{
GroupTangentValue += PolyTangent;
}
if (!PolyBinormal.IsNearlyZero(SMALL_NUMBER) && !PolyBinormal.ContainsNaN())
{
GroupBiNormalValue += PolyBinormal;
}
}
}
//////////////////////////////////////////////////////////////////////////
//Apply the group to the Mesh
GroupNormal.Normalize();
if (bComputeTangent)
{
for (auto Kvp : GroupTangent)
{
FVector& GroupTangentValue = GroupTangent.FindOrAdd(Kvp.Key);
GroupTangentValue.Normalize();
}
for (auto Kvp : GroupBiNormal)
{
FVector& GroupBiNormalValue = GroupBiNormal.FindOrAdd(Kvp.Key);
GroupBiNormalValue.Normalize();
}
}
//Apply the average NTB on all Vertex instance
for (const FVertexInstanceID VertexInstanceID : VertexInstanceInGroup)
{
const FVector2D VertexUV = VertexUVs.Get(VertexInstanceID, 0); // UV0
if (VertexNormals[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
VertexNormals[VertexInstanceID] = GroupNormal;
}
if (bComputeTangent)
{
//Avoid changing the original group value
FVector GroupTangentValue = GroupTangent[VertexUV];
FVector GroupBiNormalValue = GroupBiNormal[VertexUV];
if (!VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
GroupTangentValue = VertexTangents[VertexInstanceID];
}
FVector BiNormal(0.0f);
if (!VertexNormals[VertexInstanceID].IsNearlyZero(SMALL_NUMBER) && !VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
BiNormal = FVector::CrossProduct(VertexNormals[VertexInstanceID], VertexTangents[VertexInstanceID]).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID];
}
if (!BiNormal.IsNearlyZero(SMALL_NUMBER))
{
GroupBiNormalValue = BiNormal;
}
// Gram-Schmidt orthogonalization
GroupBiNormalValue -= GroupTangentValue * (GroupTangentValue | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
GroupTangentValue -= VertexNormals[VertexInstanceID] * (VertexNormals[VertexInstanceID] | GroupTangentValue);
GroupTangentValue.Normalize();
GroupBiNormalValue -= VertexNormals[VertexInstanceID] * (VertexNormals[VertexInstanceID] | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
//Set the value
VertexTangents[VertexInstanceID] = GroupTangentValue;
//If the BiNormal is zero set the sign to 1.0f
VertexBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(GroupTangentValue, GroupBiNormalValue, VertexNormals[VertexInstanceID]);
}
}
}
}
}
namespace MeshDescriptionMikktSpaceInterface
{
//Mikk t spce static function
int MikkGetNumFaces(const SMikkTSpaceContext* Context);
int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx);
void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx);
void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx);
void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx);
void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx);
}
void FMeshDescriptionOperations::CreateMikktTangents(FMeshDescription& MeshDescription, FMeshDescriptionOperations::ETangentOptions TangentOptions)
{
bool bIgnoreDegenerateTriangles = (TangentOptions & FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles) != 0;
// we can use mikktspace to calculate the tangents
SMikkTSpaceInterface MikkTInterface;
MikkTInterface.m_getNormal = MeshDescriptionMikktSpaceInterface::MikkGetNormal;
MikkTInterface.m_getNumFaces = MeshDescriptionMikktSpaceInterface::MikkGetNumFaces;
MikkTInterface.m_getNumVerticesOfFace = MeshDescriptionMikktSpaceInterface::MikkGetNumVertsOfFace;
MikkTInterface.m_getPosition = MeshDescriptionMikktSpaceInterface::MikkGetPosition;
MikkTInterface.m_getTexCoord = MeshDescriptionMikktSpaceInterface::MikkGetTexCoord;
MikkTInterface.m_setTSpaceBasic = MeshDescriptionMikktSpaceInterface::MikkSetTSpaceBasic;
MikkTInterface.m_setTSpace = nullptr;
SMikkTSpaceContext MikkTContext;
MikkTContext.m_pInterface = &MikkTInterface;
MikkTContext.m_pUserData = (void*)(&MeshDescription);
MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
genTangSpaceDefault(&MikkTContext);
}
namespace MeshDescriptionMikktSpaceInterface
{
int MikkGetNumFaces(const SMikkTSpaceContext* Context)
{
FMeshDescription *MeshDescription = (FMeshDescription*)(Context->m_pUserData);
return MeshDescription->Polygons().GetArraySize();
}
int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx)
{
// All of our meshes are triangles.
FMeshDescription *MeshDescription = (FMeshDescription*)(Context->m_pUserData);
if (MeshDescription->IsPolygonValid(FPolygonID(FaceIdx)))
{
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
return Polygon.PerimeterContour.VertexInstanceIDs.Num();
}
return 0;
}
void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVertexID VertexID = MeshDescription->GetVertexInstanceVertex(VertexInstanceID);
const FVector& VertexPosition = MeshDescription->VertexAttributes().GetAttribute<FVector>(VertexID, MeshAttribute::Vertex::Position);
Position[0] = VertexPosition.X;
Position[1] = VertexPosition.Y;
Position[2] = VertexPosition.Z;
}
void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector& VertexNormal = MeshDescription->VertexInstanceAttributes().GetAttribute<FVector>(VertexInstanceID, MeshAttribute::VertexInstance::Normal);
Normal[0] = VertexNormal.X;
Normal[1] = VertexNormal.Y;
Normal[2] = VertexNormal.Z;
}
void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector VertexTangent(Tangent[0], Tangent[1], Tangent[2]);
MeshDescription->VertexInstanceAttributes().SetAttribute<FVector>(VertexInstanceID, MeshAttribute::VertexInstance::Tangent, 0, VertexTangent);
MeshDescription->VertexInstanceAttributes().SetAttribute<float>(VertexInstanceID, MeshAttribute::VertexInstance::BinormalSign, 0, -BitangentSign);
}
void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector2D& TexCoord = MeshDescription->VertexInstanceAttributes().GetAttribute<FVector2D>(VertexInstanceID, MeshAttribute::VertexInstance::TextureCoordinate, 0);
UV[0] = TexCoord.X;
UV[1] = TexCoord.Y;
}
}
void FMeshDescriptionOperations::FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const FMeshDescription& MeshDescription, float ComparisonThreshold)
{
// @todo: this should be shared with FOverlappingCorners
const FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
const FVertexArray& VertexArray = MeshDescription.Vertices();
const int32 NumWedges = VertexInstanceArray.Num();
// Empty the old data and reserve space for new
OutOverlappingCorners.Init(NumWedges);
// Create a list of vertex Z/index pairs
TArray<MeshDescriptionOperationNamespace::FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumWedges);
TVertexAttributesConstRef<FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
for (const FVertexInstanceID VertexInstanceID : VertexInstanceArray.GetElementIDs())
{
new(VertIndexAndZ)MeshDescriptionOperationNamespace::FIndexAndZ(VertexInstanceID.GetValue(), VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)]);
}
// Sort the vertices by z value
VertIndexAndZ.Sort(MeshDescriptionOperationNamespace::FCompareIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > ComparisonThreshold)
break; // can't be any more dups
const FVector& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector& PositionB = *(VertIndexAndZ[j].OriginalVector);
if (PositionA.Equals(PositionB, ComparisonThreshold))
{
OutOverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
OutOverlappingCorners.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
}
}
}
OutOverlappingCorners.FinishAdding();
}
struct FLayoutUVMeshDescriptionView final : FLayoutUV::IMeshView
{
FMeshDescription& MeshDescription;
TVertexAttributesConstRef<FVector> Positions;
TVertexInstanceAttributesConstRef<FVector> Normals;
TVertexInstanceAttributesRef<FVector2D> TexCoords;
const uint32 SrcChannel;
const uint32 DstChannel;
uint32 NumIndices = 0;
TArray<int32> RemapVerts;
TArray<FVector2D> FlattenedTexCoords;
FLayoutUVMeshDescriptionView(FMeshDescription& InMeshDescription, uint32 InSrcChannel, uint32 InDstChannel)
: MeshDescription(InMeshDescription)
, Positions(InMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position))
, Normals(InMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal))
, TexCoords(InMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate))
, SrcChannel(InSrcChannel)
, DstChannel(InDstChannel)
{
uint32 NumTris = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
NumTris += MeshDescription.GetPolygonTriangles(PolygonID).Num();
}
NumIndices = NumTris * 3;
FlattenedTexCoords.SetNumUninitialized(NumIndices);
RemapVerts.SetNumUninitialized(NumIndices);
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
const TArray<FMeshTriangle>& Triangles = MeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle MeshTriangle : Triangles)
{
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(Corner);
FlattenedTexCoords[WedgeIndex] = TexCoords.Get(VertexInstanceID, SrcChannel);
RemapVerts[WedgeIndex] = VertexInstanceID.GetValue();
++WedgeIndex;
}
}
}
}
uint32 GetNumIndices() const override { return NumIndices; }
FVector GetPosition(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
return Positions[VertexID];
}
FVector GetNormal(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
return Normals[VertexInstanceID];
}
FVector2D GetInputTexcoord(uint32 Index) const override
{
return FlattenedTexCoords[Index];
}
void InitOutputTexcoords(uint32 Num) override
{
// If current DstChannel is out of range of the number of UVs defined by the mesh description, change the index count accordingly
const uint32 NumUVs = TexCoords.GetNumIndices();
if (DstChannel >= NumUVs)
{
TexCoords.SetNumIndices(DstChannel + 1);
ensure(false); // not expecting it to get here
}
}
void SetOutputTexcoord(uint32 Index, const FVector2D& Value) override
{
const FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
TexCoords.Set(VertexInstanceID, DstChannel, Value);
}
};
void FMeshDescriptionOperations::CreateLightMapUVLayout(FMeshDescription& MeshDescription,
int32 SrcLightmapIndex,
int32 DstLightmapIndex,
int32 MinLightmapResolution,
ELightmapUVVersion LightmapUVVersion,
const FOverlappingCorners& OverlappingCorners)
{
FLayoutUVMeshDescriptionView MeshDescriptionView(MeshDescription, SrcLightmapIndex, DstLightmapIndex);
FLayoutUV Packer(MeshDescriptionView, MinLightmapResolution);
Packer.SetVersion(LightmapUVVersion);
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
}
}
bool FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(const FMeshDescription& MeshDescription, int32 TextureResolution, TArray<FVector2D>& OutTexCoords)
{
// Create a copy of original mesh (only copy necessary data)
FMeshDescription DuplicateMeshDescription(MeshDescription);
// Find overlapping corners for UV generator. Allow some threshold - this should not produce any error in a case if resulting
// mesh will not merge these vertices.
FOverlappingCorners OverlappingCorners;
FindOverlappingCorners(OverlappingCorners, DuplicateMeshDescription, THRESH_POINTS_ARE_SAME);
// Generate new UVs
FLayoutUVMeshDescriptionView DuplicateMeshDescriptionView(DuplicateMeshDescription, 0, 1);
FLayoutUV Packer(DuplicateMeshDescriptionView, FMath::Clamp(TextureResolution / 4, 32, 512));
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = DuplicateMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
// Save generated UVs
check(VertexInstanceUVs.GetNumIndices() > 1);
OutTexCoords.AddZeroed(VertexInstanceUVs.GetNumElements());
int32 TextureCoordIndex = 0;
for (const FVertexInstanceID& VertexInstanceID : DuplicateMeshDescription.VertexInstances().GetElementIDs())
{
OutTexCoords[TextureCoordIndex++] = VertexInstanceUVs.Get(VertexInstanceID, 1); // UV1
}
}
return bPackSuccess;
}
bool FMeshDescriptionOperations::AddUVChannel(FMeshDescription& MeshDescription)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumIndices() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("AddUVChannel: Cannot add UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.SetNumIndices(VertexInstanceUVs.GetNumIndices() + 1);
return true;
}
bool FMeshDescriptionOperations::InsertUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (UVChannelIndex < 0 || UVChannelIndex > VertexInstanceUVs.GetNumIndices())
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
if (VertexInstanceUVs.GetNumIndices() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.InsertIndex(UVChannelIndex);
return true;
}
bool FMeshDescriptionOperations::RemoveUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumIndices() == 1)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. There must be at least one channel."));
return false;
}
if (UVChannelIndex < 0 || UVChannelIndex >= VertexInstanceUVs.GetNumIndices())
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
VertexInstanceUVs.RemoveIndex(UVChannelIndex);
return true;
}
void FMeshDescriptionOperations::GeneratePlanarUV(const FMeshDescription& MeshDescription, const FUVMapSettings& Settings, TArray<FVector2D>& OutTexCoords)
{
FVector U = FVector::ForwardVector;
FVector V = FVector::RightVector;
switch (Settings.Axis)
{
case 0:
// Project along X-axis (left view), UV along Z Y axes
U = FVector::UpVector;
V = FVector::RightVector;
break;
case 1:
// Project along Y-axis (front view), UV along X -Z axes
U = FVector::ForwardVector;
V = -FVector::UpVector;
break;
case 2:
// Project along Z-axis (top view), UV along X Y axes
U = FVector::ForwardVector;
V = FVector::RightVector;
break;
}
TMeshAttributesConstRef<FVertexID, FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
OutTexCoords.AddZeroed(MeshDescription.VertexInstances().Num());
FVector Size = Settings.Size * Settings.Scale;
FVector Offset = Settings.Position - Size / 2.f;
FQuat Rotation(Settings.RotationAxis, FMath::DegreesToRadians(Settings.RotationAngle));
int32 TextureCoordIndex = 0;
for (const FVertexInstanceID& VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector Vertex = VertexPositions[VertexID];
// Apply the gizmo transforms
Vertex = Rotation.RotateVector(Vertex);
Vertex -= Offset;
Vertex /= Size;
float UCoord = FVector::DotProduct(Vertex, U) * Settings.UVTile.X;
float VCoord = FVector::DotProduct(Vertex, V) * Settings.UVTile.Y;
OutTexCoords[TextureCoordIndex++] = FVector2D(UCoord, VCoord);
}
}
void FMeshDescriptionOperations::GenerateCylindricalUV(FMeshDescription& MeshDescription, const FUVMapSettings& Settings, TArray<FVector2D>& OutTexCoords)
{
FVector Size = Settings.Size * Settings.Scale;
FVector Offset = Settings.Position;
FVector U;
FVector V;
switch (Settings.Axis)
{
case 0:
// Cylinder along Y-axis, counterclockwise from -Z axis as seen from back view
V = FVector::RightVector;
Offset.Y -= Size.Y / 2.f;
break;
case 1:
// Cylinder along X-axis, counterclockwise from -Y axis as seen from left view
V = FVector::ForwardVector;
Offset.X -= Size.X / 2.f;
break;
case 2:
// Cylinder along Z-axis, counterclockwise from -Y axis as seen from top view
V = -FVector::UpVector;
Offset.Z -= Size.Z / 2.f;
break;
}
TMeshAttributesConstRef<FVertexID, FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
OutTexCoords.AddZeroed(MeshDescription.VertexInstances().Num());
const float AngleOffset = PI; // offset to get the same result as in 3dsmax
int32 TextureCoordIndex = 0;
FQuat Rotation(Settings.RotationAxis, FMath::DegreesToRadians(Settings.RotationAngle));
for (const FVertexInstanceID& VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector Vertex = VertexPositions[VertexID];
// Apply the gizmo transforms
Vertex = Rotation.RotateVector(Vertex);
Vertex -= Offset;
Vertex /= Size;
float Angle = 0.f;
switch (Settings.Axis)
{
case 0:
Angle = FMath::Atan2(Vertex.X, Vertex.Z);
break;
case 1:
Angle = FMath::Atan2(Vertex.Z, Vertex.Y);
break;
case 2:
Angle = FMath::Atan2(Vertex.X, Vertex.Y);
break;
}
Angle += AngleOffset;
Angle *= Settings.UVTile.X;
float UCoord = Angle / (2 * PI);
float VCoord = FVector::DotProduct(Vertex, V) * Settings.UVTile.Y;
OutTexCoords[TextureCoordIndex++] = FVector2D(UCoord, VCoord);
}
// Fix the UV coordinates for triangles at the seam where the angle wraps around
for (const FPolygonID& PolygonID : MeshDescription.Polygons().GetElementIDs())
{
const TArray<FVertexInstanceID>& VertexInstances = MeshDescription.GetPolygonPerimeterVertexInstances(PolygonID);
int32 NumInstances = VertexInstances.Num();
if (NumInstances >= 2)
{
for (int32 StartIndex = 1; StartIndex < NumInstances; ++StartIndex)
{
int32 EndIndex = StartIndex + 1;
if (EndIndex >= NumInstances)
{
EndIndex = EndIndex % NumInstances;
}
const FVector2D& StartUV = OutTexCoords[VertexInstances[StartIndex].GetValue()];
FVector2D& EndUV = OutTexCoords[VertexInstances[EndIndex].GetValue()];
// TODO: Improve fix for UVTile other than 1
float Threshold = 0.5f / Settings.UVTile.X;
if (FMath::Abs(EndUV.X - StartUV.X) > Threshold)
{
// Fix the U coordinate to get the texture go counterclockwise
if (EndUV.X > Threshold)
{
EndUV.X -= 1.f;
}
else
{
EndUV.X += 1.f;
}
}
}
}
}
}
void FMeshDescriptionOperations::GenerateBoxUV(const FMeshDescription& MeshDescription, const FUVMapSettings& Settings, TArray<FVector2D>& OutTexCoords)
{
FVector Size = Settings.Size * Settings.Scale;
FVector HalfSize = Size / 2.0f;
FVector Offset = Settings.Position - HalfSize;
FVector HintU;
FVector HintV;
switch (Settings.Axis)
{
case 0:
HintU = FVector::UpVector;
HintV = FVector::RightVector;
break;
case 1:
HintU = FVector::ForwardVector;
HintV = -FVector::UpVector;
break;
case 2:
HintU = FVector::ForwardVector;
HintV = FVector::RightVector;
break;
}
TMeshAttributesConstRef<FVertexID, FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
OutTexCoords.AddZeroed(MeshDescription.VertexInstances().Num());
TArray<FPlane> BoxPlanes;
const FVector& Center = Settings.Position;
BoxPlanes.Add(FPlane(Center + FVector(0, 0, HalfSize.Z), FVector::UpVector)); // Top plane
BoxPlanes.Add(FPlane(Center - FVector(0, 0, HalfSize.Z), -FVector::UpVector)); // Bottom plane
BoxPlanes.Add(FPlane(Center + FVector(HalfSize.X, 0, 0), FVector::ForwardVector)); // Right plane
BoxPlanes.Add(FPlane(Center - FVector(HalfSize.X, 0, 0), -FVector::ForwardVector)); // Left plane
BoxPlanes.Add(FPlane(Center + FVector(0, HalfSize.Y, 0), FVector::RightVector)); // Front plane
BoxPlanes.Add(FPlane(Center - FVector(0, HalfSize.Y, 0), -FVector::RightVector)); // Back plane
FQuat Rotation(Settings.RotationAxis, FMath::DegreesToRadians(Settings.RotationAngle));
// For each polygon, find the box plane that best matches the polygon normal
for (const FPolygonID& PolygonID : MeshDescription.Polygons().GetElementIDs())
{
const TArray<FVertexInstanceID>& VertexInstances = MeshDescription.GetPolygonPerimeterVertexInstances(PolygonID);
check(VertexInstances.Num() == 3);
FVector Vertex0 = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstances[0])];
FVector Vertex1 = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstances[1])];
FVector Vertex2 = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstances[2])];
FPlane PolygonPlane(Vertex0, Vertex2, Vertex1);
// Find the box plane that is most aligned with the polygon plane
// TODO: Also take the distance between the planes into consideration
float MaxProj = 0.f;
int32 BestPlaneIndex = 0;
for (int32 Index = 0; Index < BoxPlanes.Num(); ++Index)
{
float Proj = FVector::DotProduct(BoxPlanes[Index], PolygonPlane);
if (Proj > MaxProj)
{
MaxProj = Proj;
BestPlaneIndex = Index;
}
}
const FPlane& BestPlane = BoxPlanes[BestPlaneIndex];
FVector U = HintU;
FVector V = BestPlane ^ HintU;
if (V.IsZero())
{
// Plane normal and U were aligned, so try with V instead
U = HintV;
V = BestPlane ^ HintV;
}
for (const FVertexInstanceID& VertexInstanceID : VertexInstances)
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector Vertex = VertexPositions[VertexID];
// Apply the gizmo transforms
Vertex = Rotation.RotateVector(Vertex);
Vertex -= Offset;
Vertex /= Size;
float UCoord = FVector::DotProduct(Vertex, U) * Settings.UVTile.X;
float VCoord = FVector::DotProduct(Vertex, V) * Settings.UVTile.Y;
OutTexCoords[VertexInstanceID.GetValue()] = FVector2D(UCoord, VCoord);
}
}
}
#undef LOCTEXT_NAMESPACE