You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#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]
1506 lines
60 KiB
C++
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
|