Files
UnrealEngineUWP/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp
Aaron McLeran 6afdb03acc Copying //UE4/Dev-AnimPhys to //UE4/Dev-Main (Source: //UE4/Dev-AnimPhys @ 4062005)
#lockdown Nick.Penwarden

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

Change 4005617 by Danny.Bouimad

	Fixing TM-AnimPhys lighting so it works on all platforms
	#jira UEENGQA-19924

Change 4014898 by Aaron.McLeran

	Adding simple delay feature

Change 4025071 by Lina.Halper

	Fix and more potential fix for invalid bone index issue

	http://crashreporter/Buggs/Show/2052839
	http://crashreporter/Crashes/Show/46656562

	#jira: UE-51931

Change 4042493 by Lina.Halper

	Fix issue with sequence evaluator to handle properly when jumps from end to front or front to end

	#jira: UE-58429

Change 4042892 by Lina.Halper

	Fix issue with being able to drag/drop montage onto anim track in sequencer
	#jira: UE-57863

Change 4043553 by Ethan.Geller

	#jira  UE-58340 Handle calls to FVoiceCaptureWindows::GetVoiceData outside of existing data calls. #rb none

Change 4043613 by Lina.Halper

	Fix issue with incorrect usage of staticclass

	#jira: UE-54413

Change 4044069 by James.Golding

	PR #4455: Add FAnimNode_StateMachine subclassing support. (Contributed by redfeatherplusplus)

	#jira UE-54599

Change 4044070 by James.Golding

	PR #4349: Final points on a line test were broken, changed Plane.PlaneDot to FM. (Contributed by DSDambuster)

	#jira UE-53554

Change 4044072 by James.Golding

	Add ENGINE_API to UPhysicsHandleComponent so it can be subclassed
	#jira UE-56397

Change 4044073 by James.Golding

	PR #4611: Expose a few API's so it's possible to make custom anim graph nodes using these objects. (Contributed by ill)
	#jira UE-57004

Change 4044075 by James.Golding

	PR #4618: Bugfix: typo in path for CustomMeshComponent (case error) (Contributed by malavon)
	#jira UE-57077

Change 4044077 by James.Golding

	Add ClassGroup to some components
	#jira UE-57587, UE-57609

Change 4044080 by James.Golding

	PR #4515: Adding API export macro to ACableActor (Contributed by maxtunel)
	#jira UE-55515

Change 4044082 by James.Golding

	Remove unused CopySkinnedModelData function
	#jira UE-57623

Change 4044083 by James.Golding

	Fix per-poly collision for skel meshes. Make sure to call UpdateKinematicBonesToAnim if bEnablePerPolyCollision is set, even if no bodies
	Integration of CL 3971421 from Release-4.19 stream
	#jira UE-56405

Change 4044084 by James.Golding

	Add option to preview 'fixed bounds' in SkelMesh editors.

Change 4044086 by James.Golding

	Remove unused RigidInfluenceIndex from CPU skinning code

Change 4044310 by James.Golding

	Roll back changes to make PhysX cool fails a log instead of warning (CL 3995372, UE-56466), now that content is fixed

Change 4044416 by Lina.Halper

	Provide BP interface to get curve list of names

	#jira: UE-52623

Change 4044419 by Lina.Halper

	added notification for updating pose asset

	#jira: UE-56233

Change 4046929 by Ethan.Geller

	#jira none add my developer folder to QAGame. #fyi dan.reynolds

Change 4047064 by Ethan.Geller

	[Dev-AnimPhys] #jira UE-57890 add additional binaries for Steam Audio to LibPhonon.Build.cs. #rb none

Change 4047564 by Lina.Halper

	Fix issue of not regenerating when reimport mesh

	#jira: UE-58284

Change 4047630 by Ethan.Geller

	Fix syntax error in libPhonon. #jira none #rb none

Change 4048050 by Thomas.Sarkanen

	Allowed "Follow Bone" to be popped out of the menu into the viewport

	This allows for quick re-selection of the bone to follow, avoiding multiple clicks
	Tweaks and extends the "pinned command list" system to allow dynamic text in labels and labels to be hidden.

	#jira UE-53070 - Follow bone - Follow selected bone

Change 4048064 by Thomas.Sarkanen

	Validate any bone references during compilation

	Bone references that are set to something other than NAME_None will be verified against the skeleton.
	Updated various anim nodes to call the base class ValidateAnimNodeDuringCompilation

	#jira UE-55680 - Anim graph nodes that use FBoneReference all need validation in ValidateAnimNodeDuringCompilation

Change 4048468 by James.Golding

	PR #4319: Allow UAnimNotify_PlayMontageNotify to be inherited by other dlls (Contributed by DSDambuster)
	#jira UE-53390

Change 4048470 by James.Golding

	Implement ApplyWorldOffset to CableComponent, to handle origin shifting
	#jira UE-53560

Change 4048471 by James.Golding

	PR #4396: fix cachebones for subclasses of FAnimNode_SkeletalControlBase (Contributed by tmiv)
	#jira UE-53799

Change 4048474 by James.Golding

	PR #4423: Rename confusing argument in LineBoxIntersection (Contributed by Hybrid0)
	#jira UE-54145

Change 4048485 by James.Golding

	Fix compile error display from PoseDriver node
	#jira UE-58306

Change 4048489 by James.Golding

	Finish support for ProceduralMeshComponent supporting multiple UV channels
	#jira UE-54049

Change 4048678 by Thomas.Sarkanen

	Allowing blend space samples to be optionally moved off-grid

	Grid samples are now each optionally snapped.

	#jira UE-56116 - Allow blend spaces to optionally use off-grid sample points

Change 4048773 by Lina.Halper

	Support pose factory with name input

	#jira: UE-55859

Change 4048844 by David.Hill

	Material Proxy Settings

	Updating the max on the material proxy texture size - old value could cause int32 overflow.

	#jira: UE-55441

Change 4049464 by Lina.Halper

	update curve is expensive, and we're doing multiple times with same curve sets. I'm changing it so that it only updates main, and copy from main instance to sub/post.

	#jira: UE-58459

Change 4050939 by Aaron.McLeran

	PR #4649: Activated reverbs will now take priority when world settings are used (i.e. no volume proxy is in use) (Contributed by Brandon-Wilson)

	#jira UE-57546

Change 4050954 by Aaron.McLeran

	PR #4594: Added class type to allow inherited versions of UAudioComponents to be created (Contributed by korypostma)

	#jira UE-56454

Change 4050960 by Aaron.McLeran

	Attempt to fix linux build.

Change 4051247 by James.Golding

	Fix ProcMeshComp UpdateSection not copying all UV sets
	Add test case for ProcMeshComp with multiple UVs

	#jira UE-54049

Change 4051250 by James.Golding

	Add bUseHighPrecisionTangentBasis option to SkeletalMesh
	Change SkeletalMesh source data to store tangents at higher precision
	#jira UE-58525

Change 4051616 by Thomas.Sarkanen

	Mass scale is no longer incorrectly clamped

	This now allows mass scales below 0.01 and above 100.

	#jira UE-49572 - MassScale has some edge cases for skeletal mesh component and small numbers

Change 4051619 by Thomas.Sarkanen

	Fixed notify drag/drop on high DPI displays

	#jira UE-55690 - Animation Notifies Do Not Move Past the Center of Timeline On a High DPI Display

Change 4051626 by Thomas.Sarkanen

	Fix anim dynamics debug rendering

	#jira UE-53902 - Anim Dynamics node is missing wireframe simulation box in preview
	#jira UE-57983 - GitHub 4674 : UE-57910 Fix the angular limits display issue while selecting the AnimDynamics node

Change 4051628 by Thomas.Sarkanen

	Constraints and bodies now rotate in their own local space in the physics asset editor

	When local coordinate system is applied

	#jira UE-50345 - rotating constraints or bodies in Phat with local axis

Change 4051634 by Thomas.Sarkanen

	Automatic rules for state transitions are now shown in tooltips

	#jira UE-57689 - Animation State Machine Transitions that use bAutomaticRuleBasedOnSequencePlayerInState, should indicate that in the transition

Change 4051636 by Thomas.Sarkanen

	NotifyTriggerChance is now hidden for nodify states as it has no effect

	#jira UE-55351 - NotifyTriggerChance should be grayed out for UAnimNotifyState

Change 4051669 by Thomas.Sarkanen

	Fixed accidental operation of pinned commands when closing them

	#jira UE-54051 - Unpinning settings will toggle the next setting

Change 4051671 by Thomas.Sarkanen

	Fix crash importing skeletal mesh with no vertices

	Not a fix for the jira, but found while investigating

	#jira UE-56330 - FBX Files Do Not Import After Using the Facial Anim Importer Unless Project is Reopened

Change 4051684 by James.Golding

	Fix high precision tangents when CPU skinning and mesh merging
	Remember bExistingUseHighPrecisionTangentBasis when re-importing SkelMesh
	#jira UE-58525

Change 4051686 by James.Golding

	PR #4297: Output animation name with ensure() - useful when debugging (Contributed by DSDambuster)
	#jira UE-53259

Change 4051801 by Jurre.deBaare

	A BlendSpace that puts the same asset on samples can stop its own animation on Switch
	#fix Ensure that we don't cause divide-by-zero situations when sampling blendspace data
	#jira UE-54030

Change 4051806 by Jurre.deBaare

	Fix geometry cache reimport + serialization issues

Change 4051807 by Jurre.deBaare

	Currently, it's not possible to assigned a material to a Geometry Cache .uasset
	#fix EditAnywhere rather than VisibleAnywhere
	#jira UE-58212

Change 4051809 by Jurre.deBaare

	GeomCache: Crash/Bug: When importing file
	#fix Ensure that we have a valid first frame when trying to import a sequence, if not error-out
	#jira UE-58285

Change 4051813 by Jurre.deBaare

	GeomCache: Bug: Normals Broken
	#jira UE-58287
	GeomCache - Normals are Bad on Import
	#jira UE-58283
	#fix ensure that we triangulate mesh attributes when necessary

	#misc per-attribute indices check

Change 4051816 by Jurre.deBaare

	Alembic QOL
	- Fix issue with reimport object flags not being applied
	- Now also store sampling data as part of Alembic asset import data

Change 4051817 by Jurre.deBaare

	PR #4550: Fixes bug where "Merge Actors" or HLOD proxies result in too many mesh sections (Contributed by trond)
	#fix Integrated pull-request in different form
	#jira UE-55976

Change 4051818 by Jurre.deBaare

	Emissive isn't baked correctly in TM-MeshbakeMap
	#fix ensure that we OR and Max the material flags and emissive scale
	#jira UE-54889

Change 4051819 by Jurre.deBaare

	Crash on project load when GeometryCache plugin is disabled
	#fix No longer force-load the geometry cache module as it was moved to be a plugin
	#jira UE-57875

Change 4051820 by Jurre.deBaare

	CLONE - Editor crash when Propagating Vertex Colors to Asset's source mesh
	#fix IsValidIndex check
	#jira UE-57127

	Vertex painting

Change 4051828 by Jurre.deBaare

	Merging negative-scaled actors breaks materials
	#fix Make sure we also reverse the section indices when a static mesh has a mirrored transform
	#jira UE-56953

Change 4051834 by Jurre.deBaare

	Unclear warnings when generating clusters in persistent level when sublevels have HLOD disabled
	#fix improved warning text + added uobject link to level in content browser
	#jira UE-55734

Change 4051993 by Jurre.deBaare

	Update Alembic automated test ground truth

	#jira none

Change 4052937 by James.Golding

	Remove now-unused version (merged change to skel source data from Main instead)

Change 4053291 by Aaron.McLeran

	Fix for CIS

	#jira none

Change 4053375 by Aaron.McLeran

	#jira UE-58716 Allow ability to bypass volume-weighting with using sound wave priority

Change 4057170 by Thomas.Sarkanen

	Fix shadow variable warning

	#jira UE-58806 - Linux: Shadow Variable Warnings building Editor - PhysicsAssetEditorEditMode.cpp

Change 4057653 by Lina.Halper

	Fix the issue with showing same item multiple times when opening control rig blueprint many times

	#jira: UE-58107

Change 4057701 by Jurre.deBaare

	//UE4/Dev-AnimPhys - Step 'Run Automated Tests' has completed with 13 Errors
	#fix reupdate alembic ground truths, little bit of a weird state
	#jira UE-58818

Change 4057710 by Ethan.Geller

	[Dev-AnimPhys] #jira UE-58004 Early exit if finish was called before StartSubmixRecording. #rb Aaron.McLeran

Change 4059295 by Ethan.Geller

	#jira UE-58004 Reduce logs from fatal to error, fix serialize crash. #rb aaron.mcleran

Change 4061061 by Aaron.McLeran

	Fixing animphys build from recent merge from main.

	#jira UE-58909

Change 4053154 by Aaron.McLeran

	#jira UE-58708 Fix to mic component to reduce clicks/pops on mic input.

	Fix was to simplify the way audio is copied from mic input. This change was used on the GDC demo floor for a number of features.

[CL 4062611 by Aaron McLeran in Main branch]
2018-05-09 18:45:58 -04:00

1211 lines
42 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "MeshMergeHelpers.h"
#include "Engine/MapBuildDataRegistry.h"
#include "Engine/MeshMerging.h"
#include "MaterialOptions.h"
#include "RawMesh.h"
#include "Misc/PackageName.h"
#include "MaterialUtilities.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/SplineMeshComponent.h"
#include "Components/SkinnedMeshComponent.h"
#include "Rendering/SkeletalMeshModel.h"
#include "SkeletalMeshTypes.h"
#include "SkeletalRenderPublic.h"
#include "UObject/UObjectBaseUtility.h"
#include "UObject/Package.h"
#include "Materials/Material.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "HierarchicalLODUtilitiesModule.h"
#include "MeshMergeData.h"
#include "IHierarchicalLODUtilities.h"
#include "Engine/MeshMergeCullingVolume.h"
#include "Landscape.h"
#include "LandscapeProxy.h"
#include "Editor.h"
#include "Engine/StaticMesh.h"
#include "PhysicsEngine/ConvexElem.h"
#include "PhysicsEngine/BodySetup.h"
#include "MeshUtilities.h"
#include "ImageUtils.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "IMeshReductionManagerModule.h"
#include "LayoutUV.h"
#include "Components/InstancedStaticMeshComponent.h"
//DECLARE_LOG_CATEGORY_CLASS(LogMeshMerging, Verbose, All);
void FMeshMergeHelpers::ExtractSections(const UStaticMeshComponent* Component, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
const UStaticMesh* StaticMesh = Component->GetStaticMesh();
TArray<FName> MaterialSlotNames;
for (const FStaticMaterial& StaticMaterial : StaticMesh->StaticMaterials)
{
#if WITH_EDITOR
MaterialSlotNames.Add(StaticMaterial.ImportedMaterialSlotName);
#else
MaterialSlotNames.Add(StaticMaterial.MaterialSlotName);
#endif
}
const bool bMirrored = Component->GetComponentToWorld().GetDeterminant() < 0.f;
for (const FStaticMeshSection& MeshSection : StaticMesh->RenderData->LODResources[LODIndex].Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = Component->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
// Populate section data
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialIndex = MeshSection.MaterialIndex;
SectionInfo.MaterialSlotName = MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] : NAME_None;
SectionInfo.StartIndex = MeshSection.FirstIndex / 3;
SectionInfo.EndIndex = SectionInfo.StartIndex + MeshSection.NumTriangles;
// In case the object is mirrored the material indices/vertex data will be reversed in place, so we need to adjust the sections accordingly
if (bMirrored)
{
const uint32 NumTriangles = StaticMesh->RenderData->LODResources[LODIndex].GetNumTriangles();
SectionInfo.StartIndex = NumTriangles - SectionInfo.EndIndex;
SectionInfo.EndIndex = SectionInfo.StartIndex + MeshSection.NumTriangles;
}
if (MeshSection.bEnableCollision)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bEnableCollision));
}
if (MeshSection.bCastShadow && Component->CastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bCastShadow));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExtractSections(const USkeletalMeshComponent* Component, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
FSkeletalMeshModel* Resource = Component->SkeletalMesh->GetImportedModel();
checkf(Resource->LODModels.IsValidIndex(LODIndex), TEXT("Invalid LOD Index"));
TArray<FName> MaterialSlotNames = Component->GetMaterialSlotNames();
const FSkeletalMeshLODModel& Model = Resource->LODModels[LODIndex];
for (const FSkelMeshSection& MeshSection : Model.Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = Component->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialSlotName = MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] : NAME_None;
if (MeshSection.bCastShadow && Component->CastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bCastShadow));
}
if (MeshSection.bRecomputeTangent)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bRecomputeTangent));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExtractSections(const UStaticMesh* StaticMesh, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
for (const FStaticMeshSection& MeshSection : StaticMesh->RenderData->LODResources[LODIndex].Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = StaticMesh->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
// Populate section data
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialIndex = MeshSection.MaterialIndex;
#if WITH_EDITOR
SectionInfo.MaterialSlotName = StaticMesh->StaticMaterials.IsValidIndex(MeshSection.MaterialIndex) ? StaticMesh->StaticMaterials[MeshSection.MaterialIndex].ImportedMaterialSlotName : NAME_None;
#else
SectionInfo.MaterialSlotName = StaticMesh->StaticMaterials.IsValidIndex(MeshSection.MaterialIndex) ? StaticMesh->StaticMaterials[MeshSection.MaterialIndex].MaterialSlotName : NAME_None;
#endif
if (MeshSection.bEnableCollision)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bEnableCollision));
}
if (MeshSection.bCastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bCastShadow));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExpandInstances(const UInstancedStaticMeshComponent* InInstancedStaticMeshComponent, FRawMesh& InOutRawMesh, TArray<FSectionInfo>& InOutSections)
{
FRawMesh CombinedRawMesh;
for(const FInstancedStaticMeshInstanceData& InstanceData : InInstancedStaticMeshComponent->PerInstanceSMData)
{
FRawMesh InstanceRawMesh = InOutRawMesh;
FMeshMergeHelpers::TransformRawMeshVertexData(FTransform(InstanceData.Transform), InstanceRawMesh);
FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh);
}
InOutRawMesh = CombinedRawMesh;
}
void FMeshMergeHelpers::RetrieveMesh(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, FRawMesh& RawMesh, bool bPropagateVertexColours)
{
const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
const FStaticMeshSourceModel& StaticMeshModel = StaticMesh->SourceModels[LODIndex];
const bool bIsSplineMeshComponent = StaticMeshComponent->IsA<USplineMeshComponent>();
// Imported meshes will have a filled RawMeshBulkData set
const bool bImportedMesh = !StaticMeshModel.RawMeshBulkData->IsEmpty();
// Export the raw mesh data using static mesh render data
ExportStaticMeshLOD(StaticMesh->RenderData->LODResources[LODIndex], RawMesh);
// Make sure the raw mesh is not irreparably malformed.
if (!RawMesh.IsValid())
{
return;
}
// Use build settings from base mesh for LOD entries that was generated inside Editor.
const FMeshBuildSettings& BuildSettings = bImportedMesh ? StaticMeshModel.BuildSettings : StaticMesh->SourceModels[0].BuildSettings;
// Transform raw mesh to world space
FTransform ComponentToWorldTransform = StaticMeshComponent->GetComponentTransform();
// Handle spline mesh deformation
if (bIsSplineMeshComponent)
{
const USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(StaticMeshComponent);
// Deform raw mesh data according to the Spline Mesh Component's data
PropagateSplineDeformationToRawMesh(SplineMeshComponent, RawMesh);
}
// If specified propagate painted vertex colors into our raw mesh
if (bPropagateVertexColours)
{
PropagatePaintedColorsToRawMesh(StaticMeshComponent, LODIndex, RawMesh);
}
// Transform raw mesh vertex data by the Static Mesh Component's component to world transformation
TransformRawMeshVertexData(ComponentToWorldTransform, RawMesh);
if (!RawMesh.IsValid())
{
return;
}
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
const bool bRecomputeNormals = RawMesh.WedgeTangentZ.Num() == 0;
const bool bRecomputeTangents = RawMesh.WedgeTangentX.Num() == 0 || RawMesh.WedgeTangentY.Num() == 0;
if (bRecomputeNormals || bRecomputeTangents)
{
IMeshUtilities& Utilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
Utilities.RecomputeTangentsAndNormalsForRawMesh(bRecomputeTangents, bRecomputeNormals, BuildSettings, RawMesh);
}
}
void FMeshMergeHelpers::RetrieveMesh(USkeletalMeshComponent* SkeletalMeshComponent, int32 LODIndex, FRawMesh& RawMesh, bool bPropagateVertexColours)
{
FSkeletalMeshModel* Resource = SkeletalMeshComponent->SkeletalMesh->GetImportedModel();
if (Resource->LODModels.IsValidIndex(LODIndex))
{
FSkeletalMeshLODInfo& SrcLODInfo = *(SkeletalMeshComponent->SkeletalMesh->GetLODInfo(LODIndex));
// Get the CPU skinned verts for this LOD
TArray<FFinalSkinVertex> FinalVertices;
SkeletalMeshComponent->GetCPUSkinnedVertices(FinalVertices, LODIndex);
FSkeletalMeshLODModel& LODModel = Resource->LODModels[LODIndex];
// Copy skinned vertex positions
for (int32 VertIndex = 0; VertIndex < FinalVertices.Num(); ++VertIndex)
{
RawMesh.VertexPositions.Add(FinalVertices[VertIndex].Position);
}
const int32 NumSections = LODModel.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
// Build 'wedge' info
const int32 NumWedges = SkelMeshSection.NumTriangles * 3;
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; WedgeIndex++)
{
const int32 VertexIndexForWedge = LODModel.IndexBuffer[SkelMeshSection.BaseIndex + WedgeIndex];
RawMesh.WedgeIndices.Add(VertexIndexForWedge);
const FSoftSkinVertex& SoftVertex = SkelMeshSection.SoftVertices[VertexIndexForWedge - SkelMeshSection.BaseVertexIndex];
const FFinalSkinVertex& SkinnedVertex = FinalVertices[VertexIndexForWedge];
const FVector TangentX = SkinnedVertex.TangentX.ToFVector();
const FVector TangentZ = SkinnedVertex.TangentZ.ToFVector();
const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ.ToFVector4();
const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * UnpackedTangentZ.W;
RawMesh.WedgeTangentX.Add(TangentX);
RawMesh.WedgeTangentY.Add(TangentY);
RawMesh.WedgeTangentZ.Add(TangentZ);
for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
{
if (TexCoordIndex >= MAX_TEXCOORDS)
{
RawMesh.WedgeTexCoords[TexCoordIndex].AddDefaulted();
}
else
{
RawMesh.WedgeTexCoords[TexCoordIndex].Add(SoftVertex.UVs[TexCoordIndex]);
}
}
if (bPropagateVertexColours)
{
RawMesh.WedgeColors.Add(SoftVertex.Color);
}
else
{
RawMesh.WedgeColors.Add(FColor::White);
}
}
int32 MaterialIndex = SkelMeshSection.MaterialIndex;
// use the remapping of material indices for all LODs besides the base LOD
if (LODIndex > 0 && SrcLODInfo.LODMaterialMap.IsValidIndex(SkelMeshSection.MaterialIndex))
{
MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SkelMeshSection.MaterialIndex], 0, SkeletalMeshComponent->SkeletalMesh->Materials.Num());
}
// copy face info
for (uint32 TriIndex = 0; TriIndex < SkelMeshSection.NumTriangles; TriIndex++)
{
RawMesh.FaceMaterialIndices.Add(MaterialIndex);
RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
}
}
}
}
void FMeshMergeHelpers::RetrieveMesh(const UStaticMesh* StaticMesh, int32 LODIndex, FRawMesh& RawMesh)
{
const FStaticMeshSourceModel& StaticMeshModel = StaticMesh->SourceModels[LODIndex];
// Imported meshes will have a filled RawMeshBulkData set
const bool bImportedMesh = !StaticMeshModel.IsRawMeshEmpty();
// Check whether or not this mesh has been reduced in-engine
const bool bReducedMesh = (StaticMeshModel.ReductionSettings.PercentTriangles < 1.0f);
// Trying to retrieve rawmesh from SourceStaticMeshModel was giving issues, which causes a mismatch
const bool bRenderDataMismatch = (LODIndex > 0) || StaticMeshModel.BuildSettings.bGenerateLightmapUVs;
if (bImportedMesh && !bReducedMesh && !bRenderDataMismatch)
{
StaticMeshModel.LoadRawMesh(RawMesh);
}
else
{
ExportStaticMeshLOD(StaticMesh->RenderData->LODResources[LODIndex], RawMesh);
}
// Make sure the raw mesh is not irreparably malformed.
if (!RawMesh.IsValid())
{
// wrong
bool check = true;
}
// Use build settings from base mesh for LOD entries that was generated inside Editor.
const FMeshBuildSettings& BuildSettings = bImportedMesh ? StaticMeshModel.BuildSettings : StaticMesh->SourceModels[0].BuildSettings;
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
const bool bRecomputeNormals = (bImportedMesh && BuildSettings.bRecomputeNormals) || RawMesh.WedgeTangentZ.Num() == 0;
const bool bRecomputeTangents = (bImportedMesh && BuildSettings.bRecomputeTangents) || RawMesh.WedgeTangentX.Num() == 0 || RawMesh.WedgeTangentY.Num() == 0;
if (bRecomputeNormals || bRecomputeTangents)
{
IMeshUtilities& Utilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
Utilities.RecomputeTangentsAndNormalsForRawMesh(bRecomputeTangents, bRecomputeNormals, BuildSettings, RawMesh);
}
}
void FMeshMergeHelpers::ExportStaticMeshLOD(const FStaticMeshLODResources& StaticMeshLOD, FRawMesh& OutRawMesh)
{
const int32 NumWedges = StaticMeshLOD.IndexBuffer.GetNumIndices();
const int32 NumVertexPositions = StaticMeshLOD.VertexBuffers.PositionVertexBuffer.GetNumVertices();
const int32 NumFaces = NumWedges / 3;
// Indices
StaticMeshLOD.IndexBuffer.GetCopy(OutRawMesh.WedgeIndices);
// Vertex positions
if (NumVertexPositions > 0)
{
OutRawMesh.VertexPositions.Empty(NumVertexPositions);
for (int32 PosIdx = 0; PosIdx < NumVertexPositions; ++PosIdx)
{
FVector Pos = StaticMeshLOD.VertexBuffers.PositionVertexBuffer.VertexPosition(PosIdx);
OutRawMesh.VertexPositions.Add(Pos);
}
}
// Vertex data
if (StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() > 0)
{
OutRawMesh.WedgeTangentX.Empty(NumWedges);
OutRawMesh.WedgeTangentY.Empty(NumWedges);
OutRawMesh.WedgeTangentZ.Empty(NumWedges);
const int32 NumTexCoords = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords();
for (int32 TexCoodIdx = 0; TexCoodIdx < NumTexCoords; ++TexCoodIdx)
{
OutRawMesh.WedgeTexCoords[TexCoodIdx].Empty(NumWedges);
}
for (int32 WedgeIndex : OutRawMesh.WedgeIndices)
{
FVector WedgeTangentX = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(WedgeIndex);
FVector WedgeTangentY = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(WedgeIndex);
FVector WedgeTangentZ = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(WedgeIndex);
OutRawMesh.WedgeTangentX.Add(WedgeTangentX);
OutRawMesh.WedgeTangentY.Add(WedgeTangentY);
OutRawMesh.WedgeTangentZ.Add(WedgeTangentZ);
for (int32 TexCoodIdx = 0; TexCoodIdx < NumTexCoords; ++TexCoodIdx)
{
FVector2D WedgeTexCoord = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(WedgeIndex, TexCoodIdx);
OutRawMesh.WedgeTexCoords[TexCoodIdx].Add(WedgeTexCoord);
}
}
}
// Vertex colors
if (StaticMeshLOD.VertexBuffers.ColorVertexBuffer.GetNumVertices() > 0)
{
OutRawMesh.WedgeColors.Empty(NumWedges);
for (int32 WedgeIndex : OutRawMesh.WedgeIndices)
{
FColor VertexColor = StaticMeshLOD.VertexBuffers.ColorVertexBuffer.VertexColor(WedgeIndex);
OutRawMesh.WedgeColors.Add(VertexColor);
}
}
// Materials
{
OutRawMesh.FaceMaterialIndices.Empty(NumFaces);
OutRawMesh.FaceMaterialIndices.SetNumZeroed(NumFaces);
for (const FStaticMeshSection& Section : StaticMeshLOD.Sections)
{
uint32 FirstTriangle = Section.FirstIndex / 3;
for (uint32 TriangleIndex = 0; TriangleIndex < Section.NumTriangles; ++TriangleIndex)
{
OutRawMesh.FaceMaterialIndices[FirstTriangle + TriangleIndex] = Section.MaterialIndex;
}
}
}
// Smoothing masks
{
OutRawMesh.FaceSmoothingMasks.Empty(NumFaces);
OutRawMesh.FaceSmoothingMasks.SetNumUninitialized(NumFaces);
for (auto& SmoothingMask : OutRawMesh.FaceSmoothingMasks)
{
SmoothingMask = 1;
}
}
}
bool FMeshMergeHelpers::CheckWrappingUVs(const TArray<FVector2D>& UVs)
{
bool bResult = false;
FVector2D Min(FLT_MAX, FLT_MAX);
FVector2D Max(-FLT_MAX, -FLT_MAX);
for (const FVector2D& Coordinate : UVs)
{
if ((FMath::IsNegativeFloat(Coordinate.X) || FMath::IsNegativeFloat(Coordinate.Y)) || (Coordinate.X > (1.0f + KINDA_SMALL_NUMBER) || Coordinate.Y > (1.0f + KINDA_SMALL_NUMBER)))
{
bResult = true;
break;
}
}
return bResult;
}
void FMeshMergeHelpers::CullTrianglesFromVolumesAndUnderLandscapes(const UWorld* World, const FBoxSphereBounds& Bounds, FRawMesh& InOutRawMesh)
{
TArray<ALandscapeProxy*> Landscapes;
TArray<AMeshMergeCullingVolume*> CullVolumes;
FBox BoxBounds = Bounds.GetBox();
for (ULevel* Level : World->GetLevels())
{
for (AActor* Actor : Level->Actors)
{
ALandscape* Proxy = Cast<ALandscape>(Actor);
if (Proxy && Proxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
// Ignore Z axis for 2d bounds check
if (LandscapeBox.IntersectXY(BoxBounds))
{
Landscapes.Add(Proxy->GetLandscapeActor());
}
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(Bounds.Origin, Bounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
TArray<bool> VertexVisible;
VertexVisible.AddZeroed(InOutRawMesh.VertexPositions.Num());
int32 Index = 0;
for (const FVector& Position : InOutRawMesh.VertexPositions)
{
// Start with setting visibility to true on all vertices
VertexVisible[Index] = true;
// Check if this vertex is culled due to being underneath a landscape
if (Landscapes.Num() > 0)
{
bool bVertexWithinLandscapeBounds = false;
for (ALandscapeProxy* Proxy : Landscapes)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
bVertexWithinLandscapeBounds |= LandscapeBox.IsInsideXY(Position);
}
if (bVertexWithinLandscapeBounds)
{
const FVector Start = Position;
FVector End = Position - (WORLD_MAX * FVector::UpVector);
FVector OutHit;
const bool IsAboveLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
End = Position + (WORLD_MAX * FVector::UpVector);
const bool IsUnderneathLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
// Vertex is visible when above landscape (with actual landscape underneath) or if there is no landscape beneath or above the vertex (falls outside of landscape bounds)
VertexVisible[Index] = (IsAboveLandscape && !IsUnderneathLandscape);// || (!IsAboveLandscape && !IsUnderneathLandscape);
}
}
// Volume culling
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
const bool bVertexIsInsideVolume = Volume->EncompassesPoint(Position, 0.0f, nullptr);
if (bVertexIsInsideVolume)
{
// Inside a culling volume so invisible
VertexVisible[Index] = false;
}
}
Index++;
}
// We now know which vertices are below the landscape
TArray<bool> TriangleVisible;
int32 NumTriangles = InOutRawMesh.WedgeIndices.Num() / 3;
TriangleVisible.AddZeroed(NumTriangles);
bool bCreateNewMesh = false;
// Determine which triangles of the mesh are visible
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
{
bool AboveLandscape = false;
for (int32 WedgeIndex = 0; WedgeIndex < 3; ++WedgeIndex)
{
AboveLandscape |= VertexVisible[InOutRawMesh.WedgeIndices[(TriangleIndex * 3) + WedgeIndex]];
}
TriangleVisible[TriangleIndex] = AboveLandscape;
bCreateNewMesh |= !AboveLandscape;
}
// Check whether or not we have to create a new mesh
if (bCreateNewMesh)
{
FRawMesh NewRawMesh;
TMap<int32, int32> VertexRemapping;
// Fill new mesh with data only from visible triangles
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
if (!TriangleVisible[TriangleIndex])
continue;
for (int32 WedgeIndex = 0; WedgeIndex < 3; ++WedgeIndex)
{
int32 OldIndex = InOutRawMesh.WedgeIndices[(TriangleIndex * 3) + WedgeIndex];
int32 NewIndex;
int32* RemappedIndex = VertexRemapping.Find(Index);
if (RemappedIndex)
{
NewIndex = *RemappedIndex;
}
else
{
NewIndex = NewRawMesh.VertexPositions.Add(InOutRawMesh.VertexPositions[OldIndex]);
VertexRemapping.Add(OldIndex, NewIndex);
}
NewRawMesh.WedgeIndices.Add(NewIndex);
if (InOutRawMesh.WedgeColors.Num()) NewRawMesh.WedgeColors.Add(InOutRawMesh.WedgeColors[(TriangleIndex * 3) + WedgeIndex]);
if (InOutRawMesh.WedgeTangentX.Num()) NewRawMesh.WedgeTangentX.Add(InOutRawMesh.WedgeTangentX[(TriangleIndex * 3) + WedgeIndex]);
if (InOutRawMesh.WedgeTangentY.Num()) NewRawMesh.WedgeTangentY.Add(InOutRawMesh.WedgeTangentY[(TriangleIndex * 3) + WedgeIndex]);
if (InOutRawMesh.WedgeTangentZ.Num()) NewRawMesh.WedgeTangentZ.Add(InOutRawMesh.WedgeTangentZ[(TriangleIndex * 3) + WedgeIndex]);
for (int32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS; ++UVIndex)
{
if (InOutRawMesh.WedgeTexCoords[UVIndex].Num())
{
NewRawMesh.WedgeTexCoords[UVIndex].Add(InOutRawMesh.WedgeTexCoords[UVIndex][(TriangleIndex * 3) + WedgeIndex]);
}
}
}
NewRawMesh.FaceMaterialIndices.Add(InOutRawMesh.FaceMaterialIndices[TriangleIndex]);
NewRawMesh.FaceSmoothingMasks.Add(InOutRawMesh.FaceSmoothingMasks[TriangleIndex]);
}
InOutRawMesh = NewRawMesh;
}
}
void FMeshMergeHelpers::PropagateSplineDeformationToRawMesh(const USplineMeshComponent* InSplineMeshComponent, struct FRawMesh &OutRawMesh)
{
// Apply spline deformation for each vertex's tangents
for (int32 iVert = 0; iVert < OutRawMesh.WedgeIndices.Num(); ++iVert)
{
uint32 Index = OutRawMesh.WedgeIndices[iVert];
float& AxisValue = USplineMeshComponent::GetAxisValue(OutRawMesh.VertexPositions[Index], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
// Transform tangents first
if (OutRawMesh.WedgeTangentX.Num())
{
OutRawMesh.WedgeTangentX[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentX[iVert]);
}
if (OutRawMesh.WedgeTangentY.Num())
{
OutRawMesh.WedgeTangentY[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentY[iVert]);
}
if (OutRawMesh.WedgeTangentZ.Num())
{
OutRawMesh.WedgeTangentZ[iVert] = SliceTransform.TransformVector(OutRawMesh.WedgeTangentZ[iVert]);
}
}
// Apply spline deformation for each vertex position
for (int32 iVert = 0; iVert < OutRawMesh.VertexPositions.Num(); ++iVert)
{
float& AxisValue = USplineMeshComponent::GetAxisValue(OutRawMesh.VertexPositions[iVert], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
AxisValue = 0.0f;
OutRawMesh.VertexPositions[iVert] = SliceTransform.TransformPosition(OutRawMesh.VertexPositions[iVert]);
}
}
void FMeshMergeHelpers::PropagateSplineDeformationToPhysicsGeometry(USplineMeshComponent* SplineMeshComponent, FKAggregateGeom& InOutPhysicsGeometry)
{
const FVector Mask = USplineMeshComponent::GetAxisMask(SplineMeshComponent->GetForwardAxis());
for (FKConvexElem& Elem : InOutPhysicsGeometry.ConvexElems)
{
for (FVector& Position : Elem.VertexData)
{
const float& AxisValue = USplineMeshComponent::GetAxisValue(Position, SplineMeshComponent->ForwardAxis);
FTransform SliceTransform = SplineMeshComponent->CalcSliceTransform(AxisValue);
Position = SliceTransform.TransformPosition(Position * Mask);
}
Elem.UpdateElemBox();
}
for (FKSphereElem& Elem : InOutPhysicsGeometry.SphereElems)
{
const FVector WorldSpaceCenter = Elem.GetTransform().TransformPosition(Elem.Center);
Elem.Center = SplineMeshComponent->CalcSliceTransform(USplineMeshComponent::GetAxisValue(WorldSpaceCenter, SplineMeshComponent->ForwardAxis)).TransformPosition(Elem.Center * Mask);
}
for (FKSphylElem& Elem : InOutPhysicsGeometry.SphylElems)
{
const FVector WorldSpaceCenter = Elem.GetTransform().TransformPosition(Elem.Center);
Elem.Center = SplineMeshComponent->CalcSliceTransform(USplineMeshComponent::GetAxisValue(WorldSpaceCenter, SplineMeshComponent->ForwardAxis)).TransformPosition(Elem.Center * Mask);
}
}
void FMeshMergeHelpers::TransformRawMeshVertexData(const FTransform& InTransform, FRawMesh &OutRawMesh)
{
for (FVector& Vertex : OutRawMesh.VertexPositions)
{
Vertex = InTransform.TransformPosition(Vertex);
}
auto TransformNormal = [&](FVector& Normal)
{
FMatrix Matrix = InTransform.ToMatrixWithScale();
const float DetM = Matrix.Determinant();
FMatrix AdjointT = Matrix.TransposeAdjoint();
AdjointT.RemoveScaling();
Normal = AdjointT.TransformVector(Normal);
if (DetM < 0.f)
{
Normal *= -1.0f;
}
};
for (FVector& TangentX : OutRawMesh.WedgeTangentX)
{
TransformNormal(TangentX);
}
for (FVector& TangentY : OutRawMesh.WedgeTangentY)
{
TransformNormal(TangentY);
}
for (FVector& TangentZ : OutRawMesh.WedgeTangentZ)
{
TransformNormal(TangentZ);
}
const bool bIsMirrored = InTransform.GetDeterminant() < 0.f;
if (bIsMirrored)
{
Algo::Reverse(OutRawMesh.WedgeIndices);
Algo::Reverse(OutRawMesh.WedgeTangentX);
Algo::Reverse(OutRawMesh.WedgeTangentY);
Algo::Reverse(OutRawMesh.WedgeTangentZ);
for (uint32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS; ++UVIndex)
{
Algo::Reverse(OutRawMesh.WedgeTexCoords[UVIndex]);
}
Algo::Reverse(OutRawMesh.FaceMaterialIndices);
Algo::Reverse(OutRawMesh.FaceSmoothingMasks);
Algo::Reverse(OutRawMesh.WedgeColors);
}
}
void FMeshMergeHelpers::RetrieveCullingLandscapeAndVolumes(UWorld* InWorld, const FBoxSphereBounds& EstimatedMeshProxyBounds, const TEnumAsByte<ELandscapeCullingPrecision::Type> PrecisionType, TArray<FRawMesh*>& CullingRawMeshes)
{
// Extract landscape proxies and cull volumes from the world
TArray<ALandscapeProxy*> LandscapeActors;
TArray<AMeshMergeCullingVolume*> CullVolumes;
uint32 MaxLandscapeExportLOD = 0;
if (InWorld->IsValidLowLevel())
{
for (FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator)
{
for (AActor* Actor : (*Iterator)->Actors)
{
if (Actor)
{
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(Actor);
if (LandscapeProxy && LandscapeProxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
// Retrieve highest landscape LOD level possible
MaxLandscapeExportLOD = FMath::Max(MaxLandscapeExportLOD, FMath::CeilLogTwo(LandscapeProxy->SubsectionSizeQuads + 1) - 1);
LandscapeActors.Add(LandscapeProxy);
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(EstimatedMeshProxyBounds.Origin, EstimatedMeshProxyBounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
}
}
// Setting determines the precision at which we should export the landscape for culling (highest, half or lowest)
const uint32 LandscapeExportLOD = ((float)MaxLandscapeExportLOD * (0.5f * (float)PrecisionType));
for (ALandscapeProxy* Landscape : LandscapeActors)
{
// Export the landscape to raw mesh format
FRawMesh* LandscapeRawMesh = new FRawMesh();
FBoxSphereBounds LandscapeBounds = EstimatedMeshProxyBounds;
Landscape->ExportToRawMesh(LandscapeExportLOD, *LandscapeRawMesh, LandscapeBounds);
if (LandscapeRawMesh->VertexPositions.Num())
{
CullingRawMeshes.Add(LandscapeRawMesh);
}
}
// Also add volume mesh data as culling meshes
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
// Export the landscape to raw mesh format
FRawMesh* VolumeMesh = new FRawMesh();
TArray<FStaticMaterial> VolumeMaterials;
GetBrushMesh(Volume, Volume->Brush, *VolumeMesh, VolumeMaterials);
// Offset vertices to correct world position;
FVector VolumeLocation = Volume->GetActorLocation();
for (FVector& Position : VolumeMesh->VertexPositions)
{
Position += VolumeLocation;
}
CullingRawMeshes.Add(VolumeMesh);
}
}
void FMeshMergeHelpers::TransformPhysicsGeometry(const FTransform& InTransform, struct FKAggregateGeom& AggGeom)
{
FTransform NoScaleInTransform = InTransform;
NoScaleInTransform.SetScale3D(FVector(1, 1, 1));
// Pre-scale all non-convex geometry
const FVector Scale3D = InTransform.GetScale3D();
if (!Scale3D.Equals(FVector(1.f)))
{
const float MinPrimSize = KINDA_SMALL_NUMBER;
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
}
// Multiply out merge transform (excluding scale) with original transforms for non-convex geometry
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKConvexElem& Elem : AggGeom.ConvexElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*InTransform);
}
}
void FMeshMergeHelpers::ExtractPhysicsGeometry(UBodySetup* InBodySetup, const FTransform& ComponentToWorld, struct FKAggregateGeom& OutAggGeom)
{
if (InBodySetup == nullptr)
{
return;
}
OutAggGeom = InBodySetup->AggGeom;
// Convert boxes to convex, so they can be sheared
for (int32 BoxIdx = 0; BoxIdx < OutAggGeom.BoxElems.Num(); BoxIdx++)
{
FKConvexElem* NewConvexColl = new(OutAggGeom.ConvexElems) FKConvexElem();
NewConvexColl->ConvexFromBoxElem(OutAggGeom.BoxElems[BoxIdx]);
}
OutAggGeom.BoxElems.Empty();
// we are not owner of this stuff
OutAggGeom.RenderInfo = nullptr;
for (FKConvexElem& Elem : OutAggGeom.ConvexElems)
{
Elem.SetConvexMesh(nullptr);
Elem.SetMirroredConvexMesh(nullptr);
}
// Transform geometry to world space
TransformPhysicsGeometry(ComponentToWorld, OutAggGeom);
}
FVector2D FMeshMergeHelpers::GetValidUV(const FVector2D& UV)
{
FVector2D NewUV = UV;
// first make sure they're positive
if (UV.X < 0.0f)
{
NewUV.X = UV.X + FMath::CeilToInt(FMath::Abs(UV.X));
}
if (UV.Y < 0.0f)
{
NewUV.Y = UV.Y + FMath::CeilToInt(FMath::Abs(UV.Y));
}
// now make sure they're within [0, 1]
if (UV.X > 1.0f)
{
NewUV.X = FMath::Fmod(NewUV.X, 1.0f);
}
if (UV.Y > 1.0f)
{
NewUV.Y = FMath::Fmod(NewUV.Y, 1.0f);
}
return NewUV;
}
void FMeshMergeHelpers::CalculateTextureCoordinateBoundsForRawMesh(const FRawMesh& InRawMesh, TArray<FBox2D>& OutBounds)
{
const int32 NumWedges = InRawMesh.WedgeIndices.Num();
const int32 NumTris = NumWedges / 3;
OutBounds.Empty();
for (int32 TriIndex = 0; TriIndex < NumTris; TriIndex++)
{
int MaterialIndex = InRawMesh.FaceMaterialIndices[TriIndex];
if (OutBounds.Num() <= MaterialIndex)
OutBounds.SetNumZeroed(MaterialIndex + 1);
{
const int32 CachedWedgeIndex = TriIndex * 3;
for (int32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS; ++UVIndex)
{
int32 WedgeIndex = CachedWedgeIndex;
if (InRawMesh.WedgeTexCoords[UVIndex].Num())
{
for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++, WedgeIndex++)
{
OutBounds[MaterialIndex] += InRawMesh.WedgeTexCoords[UVIndex][WedgeIndex];
}
}
}
}
}
}
bool FMeshMergeHelpers::PropagatePaintedColorsToRawMesh(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, FRawMesh& RawMesh)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh->SourceModels.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData[LODIndex].OverrideVertexColors != nullptr)
{
FColorVertexBuffer& ColorVertexBuffer = *StaticMeshComponent->LODData[LODIndex].OverrideVertexColors;
FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[LODIndex];
if (ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices())
{
const int32 NumWedges = RawMesh.WedgeIndices.Num();
const int32 NumRenderWedges = RenderModel.IndexBuffer.GetNumIndices();
const bool bUseRenderWedges = NumWedges == NumRenderWedges;
if (bUseRenderWedges)
{
const int32 NumExistingColors = RawMesh.WedgeColors.Num();
if (NumExistingColors < NumRenderWedges)
{
RawMesh.WedgeColors.AddUninitialized(NumRenderWedges - NumExistingColors);
}
const FIndexArrayView ArrayView = RenderModel.IndexBuffer.GetArrayView();
for (int32 WedgeIndex = 0; WedgeIndex < NumRenderWedges; WedgeIndex++)
{
const int32 Index = ArrayView[WedgeIndex];
FColor WedgeColor = FColor::White;
if (Index != INDEX_NONE)
{
WedgeColor = ColorVertexBuffer.VertexColor(Index);
}
RawMesh.WedgeColors[WedgeIndex] = WedgeColor;
}
return true;
}
// No wedge map (this can happen when we poly reduce the LOD for example)
// Use index buffer directly
else
{
RawMesh.WedgeColors.SetNumUninitialized(NumWedges);
if (RawMesh.VertexPositions.Num() == ColorVertexBuffer.GetNumVertices())
{
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FColor WedgeColor = FColor::White;
uint32 VertIndex = RawMesh.WedgeIndices[WedgeIndex];
if (VertIndex < ColorVertexBuffer.GetNumVertices())
{
WedgeColor = ColorVertexBuffer.VertexColor(VertIndex);
}
RawMesh.WedgeColors[WedgeIndex] = WedgeColor;
}
return true;
}
}
}
}
return false;
}
bool FMeshMergeHelpers::IsLandscapeHit(const FVector& RayOrigin, const FVector& RayEndPoint, const UWorld* World, const TArray<ALandscapeProxy*>& LandscapeProxies, FVector& OutHitLocation)
{
TArray<FHitResult> Results;
// Each landscape component has 2 collision shapes, 1 of them is specific to landscape editor
// Trace only ECC_Visibility channel, so we do hit only Editor specific shape
World->LineTraceMultiByObjectType(Results, RayOrigin, RayEndPoint, FCollisionObjectQueryParams(ECollisionChannel::ECC_WorldStatic), FCollisionQueryParams(SCENE_QUERY_STAT(LandscapeTrace), true));
bool bHitLandscape = false;
for (const FHitResult& HitResult : Results)
{
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Cast<ULandscapeHeightfieldCollisionComponent>(HitResult.Component.Get());
if (CollisionComponent)
{
ALandscapeProxy* HitLandscape = CollisionComponent->GetLandscapeProxy();
if (HitLandscape && LandscapeProxies.Contains(HitLandscape))
{
// Could write a correct clipping algorithm, that clips the triangle to hit location
OutHitLocation = HitLandscape->LandscapeActorToWorld().InverseTransformPosition(HitResult.Location);
// Above landscape so visible
bHitLandscape = true;
}
}
}
return bHitLandscape;
}
void FMeshMergeHelpers::AppendRawMesh(FRawMesh& InTarget, const FRawMesh& InSource)
{
const int32 MaxSmoothingMask = [InTarget]() -> int32
{
int32 Max = 0;
for (const int32 Value : InTarget.FaceSmoothingMasks)
{
Max = FMath::Max(Max, Value);
}
return Max;
}();
InTarget.FaceMaterialIndices.Append(InSource.FaceMaterialIndices);
const int32 FaceOffset = InTarget.FaceSmoothingMasks.Num();
InTarget.FaceSmoothingMasks.Append(InSource.FaceSmoothingMasks);
for (int32 Index = FaceOffset; Index < InTarget.FaceSmoothingMasks.Num(); ++Index)
{
InTarget.FaceSmoothingMasks[Index] += FaceOffset;
}
const int32 VertexOffset = InTarget.VertexPositions.Num();
InTarget.VertexPositions.Append(InSource.VertexPositions);
const int32 IndexOffset = InTarget.WedgeIndices.Num();
InTarget.WedgeIndices.Append(InSource.WedgeIndices);
for (int32 Index = IndexOffset; Index < InTarget.WedgeIndices.Num(); ++Index)
{
InTarget.WedgeIndices[Index] += VertexOffset;
}
InTarget.WedgeTangentX.Append(InSource.WedgeTangentX);
InTarget.WedgeTangentY.Append(InSource.WedgeTangentY);
InTarget.WedgeTangentZ.Append(InSource.WedgeTangentZ);
// Check whether or not we have pad the wedge colors
const int32 NumTotalColors = InTarget.WedgeColors.Num() + InSource.WedgeColors.Num();
if (NumTotalColors < InTarget.WedgeTangentZ.Num() && (InSource.WedgeColors.Num() || InTarget.WedgeColors.Num()) )
{
InTarget.WedgeColors.AddZeroed(InTarget.WedgeTangentZ.Num() - NumTotalColors);
}
InTarget.WedgeColors.Append(InSource.WedgeColors);
for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i)
{
const int32 NumTotalUVs = InTarget.WedgeTexCoords[i].Num() + InSource.WedgeTexCoords[i].Num();
// Check whether or not we have pad the UVs
if ( NumTotalUVs < InTarget.WedgeTangentZ.Num() && (InSource.WedgeTexCoords[i].Num() || InTarget.WedgeTexCoords[i].Num()) )
{
InTarget.WedgeTexCoords[i].AddZeroed(InTarget.WedgeTangentZ.Num() - NumTotalUVs);
}
InTarget.WedgeTexCoords[i].Append(InSource.WedgeTexCoords[i]);
}
checkf(InTarget.IsValidOrFixable(), TEXT("RawMesh became corrupt after appending InSource"));
}
void FMeshMergeHelpers::MergeImpostersToRawMesh(TArray<const UStaticMeshComponent*> ImposterComponents, FRawMesh& InRawMesh, const FVector& InPivot, int32 InBaseMaterialIndex, TArray<UMaterialInterface*>& OutImposterMaterials)
{
// TODO decide whether we want this to be user specified or derived from the RawMesh
/*const int32 UVOneIndex = [RawMesh, Data]() -> int32
{
int32 ChannelIndex = 0;
for (; ChannelIndex < MAX_MESH_TEXTURE_COORDS; ++ChannelIndex)
{
if (RawMesh.WedgeTexCoords[ChannelIndex].Num() == 0)
{
break;
}
}
int32 MaxUVChannel = ChannelIndex;
for (const UStaticMeshComponent* Component : ImposterComponents)
{
MaxUVChannel = FMath::Max(MaxUVChannel, Component->GetStaticMesh()->RenderData->LODResources[Component->GetStaticMesh()->GetNumLODs() - 1].GetNumTexCoords());
}
return MaxUVChannel;
}();*/
const int32 UVOneIndex = 2; // if this is changed back to being dynamic, renable the if statement below
// Ensure there are enough UV channels available to store the imposter data
//if (UVOneIndex != INDEX_NONE && UVOneIndex < (MAX_MESH_TEXTURE_COORDS - 2))
{
for (const UStaticMeshComponent* Component : ImposterComponents)
{
// Retrieve imposter LOD mesh and material
const int32 LODIndex = Component->GetStaticMesh()->GetNumLODs() - 1;
// Retrieve mesh data in FRawMesh form
FRawMesh ImposterMesh;
FMeshMergeHelpers::RetrieveMesh(Component, LODIndex, ImposterMesh, false);
// Retrieve the sections, we're expect 1 for imposter meshes
TArray<FSectionInfo> Sections;
FMeshMergeHelpers::ExtractSections(Component, LODIndex, Sections);
// Generate a map of section to material index remaps
TMap<int32, int32> Remaps;
for (FSectionInfo& Info : Sections)
{
const int32 MaterialIndex = OutImposterMaterials.AddUnique(Info.Material);
// Offsetting material index by InBaseMaterialIndex
Remaps.Add(Info.MaterialIndex, MaterialIndex + InBaseMaterialIndex);
}
// Apply material remapping
for (int32& Index : ImposterMesh.FaceMaterialIndices)
{
Index = Remaps[Index];
}
// Imposter magic, we're storing the actor world position and X scale spread across two UV channels
const int32 UVTwoIndex = UVOneIndex + 1;
const int32 NumIndices = ImposterMesh.WedgeIndices.Num();
ImposterMesh.WedgeTexCoords[UVOneIndex].SetNumZeroed(NumIndices);
ImposterMesh.WedgeTexCoords[UVTwoIndex].SetNumZeroed(NumIndices);
const FTransform& ActorToWorld = Component->GetOwner()->GetActorTransform();
const FVector ActorPosition = ActorToWorld.TransformPosition(FVector::ZeroVector) - InPivot;
for (int32 Index = 0; Index < NumIndices; ++Index)
{
const int32& VertexIndex = ImposterMesh.WedgeIndices[Index];
const FVector& WedgePosition = ImposterMesh.VertexPositions[VertexIndex];
FVector2D& UVOne = ImposterMesh.WedgeTexCoords[UVOneIndex][Index];
FVector2D& UVTwo = ImposterMesh.WedgeTexCoords[UVTwoIndex][Index];
UVOne.X = ActorPosition.X;
UVOne.Y = ActorPosition.Y;
UVTwo.X = ActorPosition.Z;
UVTwo.Y = ActorToWorld.GetScale3D().X;
}
FMeshMergeHelpers::AppendRawMesh(InRawMesh, ImposterMesh);
}
}
}