forked from TriAxis-Games/RealtimeMeshComponent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRuntimeMeshComponentDetails.cpp
More file actions
323 lines (267 loc) · 11 KB
/
Copy pathRuntimeMeshComponentDetails.cpp
File metadata and controls
323 lines (267 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright 2016-2020 TriAxis Games L.L.C. All Rights Reserved.
#include "RuntimeMeshComponentDetails.h"
#include "RuntimeMeshComponent.h"
#include "Dialogs/DlgPickAssetPath.h"
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#include "AssetRegistryModule.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "PhysicsEngine/BodySetup.h"
#include "IDetailCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"
#include "RawMesh.h"
#include "Engine/StaticMesh.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SComboBox.h"
#include "RuntimeMeshProvider.h"
#include "RuntimeMeshActor.h"
#define LOCTEXT_NAMESPACE "RuntimeMeshComponentDetails"
TSharedRef<IDetailCustomization> FRuntimeMeshComponentDetails::MakeInstance()
{
return MakeShareable(new FRuntimeMeshComponentDetails);
}
void FRuntimeMeshComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
IDetailCategoryBuilder& RuntimeMeshCategory = DetailBuilder.EditCategory("RuntimeMesh");
const FText ConvertToStaticMeshText = LOCTEXT("ConvertToStaticMesh", "Create StaticMesh");
// Cache set of selected things
SelectedObjectsList = DetailBuilder.GetDetailsView()->GetSelectedObjects();
// Add the Create Static Mesh button
RuntimeMeshCategory.AddCustomRow(ConvertToStaticMeshText, false)
.NameContent()
[
SNullWidget::NullWidget
]
.ValueContent()
.VAlign(VAlign_Center)
.MaxDesiredWidth(250)
[
SNew(SButton)
.VAlign(VAlign_Center)
.ToolTipText(LOCTEXT("ConvertToStaticMeshTooltip", "Create a new StaticMesh asset using current geometry from this RuntimeMeshComponent. Does not modify instance."))
.OnClicked(this, &FRuntimeMeshComponentDetails::ClickedOnConvertToStaticMesh)
.IsEnabled(this, &FRuntimeMeshComponentDetails::ConvertToStaticMeshEnabled)
.Content()
[
SNew(STextBlock)
.Text(ConvertToStaticMeshText)
]
];
{
// Add all the default properties
TArray<TSharedRef<IPropertyHandle>> AllProperties;
bool bSimpleProperties = true;
bool bAdvancedProperties = false;
// Add all properties in the category in order
RuntimeMeshCategory.GetDefaultProperties(AllProperties, bSimpleProperties, bAdvancedProperties);
for (auto& Property : AllProperties)
{
RuntimeMeshCategory.AddProperty(Property);
}
}
TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
RuntimeMeshesReferenced.Empty();
for (TWeakObjectPtr<UObject>& Object : ObjectsBeingCustomized)
{
URuntimeMeshComponent* Component = Cast<URuntimeMeshComponent>(Object.Get());
if (ensure(Component))
{
if (Component->GetRuntimeMesh())
{
RuntimeMeshesReferenced.AddUnique(Component->GetRuntimeMesh());
}
}
}
}
URuntimeMeshComponent* FRuntimeMeshComponentDetails::GetFirstSelectedRuntimeMeshComp() const
{
// Find first selected valid RuntimeMeshComp
URuntimeMeshComponent* RuntimeMeshComp = nullptr;
for (const TWeakObjectPtr<UObject>& Object : SelectedObjectsList)
{
URuntimeMeshComponent* TestRuntimeComp = Cast<URuntimeMeshComponent>(Object.Get());
// See if this one is good
if (TestRuntimeComp != nullptr && !TestRuntimeComp->IsTemplate())
{
RuntimeMeshComp = TestRuntimeComp;
break;
}
ARuntimeMeshActor* TestRuntimeActor = Cast<ARuntimeMeshActor>(Object.Get());
if (TestRuntimeActor != nullptr && !TestRuntimeActor->IsTemplate())
{
RuntimeMeshComp = TestRuntimeActor->GetRuntimeMeshComponent();
break;
}
}
return RuntimeMeshComp;
}
bool FRuntimeMeshComponentDetails::ConvertToStaticMeshEnabled() const
{
return GetFirstSelectedRuntimeMeshComp() != nullptr;
}
FReply FRuntimeMeshComponentDetails::ClickedOnConvertToStaticMesh()
{
// Find first selected RuntimeMeshComp
URuntimeMeshComponent* RuntimeMeshComp = GetFirstSelectedRuntimeMeshComp();
URuntimeMesh* RuntimeMesh = RuntimeMeshComp->GetRuntimeMesh();
URuntimeMeshProvider* MeshProvider = RuntimeMesh->GetProviderPtr();
if (RuntimeMeshComp != nullptr && RuntimeMesh != nullptr && MeshProvider != nullptr)
{
FString NewNameSuggestion = FString(TEXT("RuntimeMeshComp"));
FString PackageName = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion;
FString Name;
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), PackageName, Name);
TSharedPtr<SDlgPickAssetPath> PickAssetPathWidget =
SNew(SDlgPickAssetPath)
.Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location"))
.DefaultAssetPath(FText::FromString(PackageName));
if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok)
{
// Get the full name of where we want to create the physics asset.
FString UserPackageName = PickAssetPathWidget->GetFullAssetPath().ToString();
FName MeshName(*FPackageName::GetLongPackageAssetName(UserPackageName));
// Check if the user inputed a valid asset name, if they did not, give it the generated default name
if (MeshName == NAME_None)
{
// Use the defaults that were already generated.
UserPackageName = PackageName;
MeshName = *Name;
}
// Create the package to save the static mesh
UPackage* Package = CreatePackage(*UserPackageName);
check(Package);
// Create StaticMesh object
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, MeshName, RF_Public | RF_Standalone);
StaticMesh->InitResources();
StaticMesh->LightingGuid = FGuid::NewGuid();
// Copy the material slots
TArray<FStaticMaterial>& Materials = StaticMesh->StaticMaterials;
const auto RMCMaterialSlots = RuntimeMesh->GetMaterialSlots();
Materials.SetNum(RMCMaterialSlots.Num());
for (int32 Index = 0; Index < RMCMaterialSlots.Num(); Index++)
{
UMaterialInterface* Mat = RuntimeMeshComp->OverrideMaterials.Num() > Index ? RuntimeMeshComp->OverrideMaterials[Index] : nullptr;
Mat = Mat ? Mat : RMCMaterialSlots[Index].Material;
Materials[Index] = FStaticMaterial(Mat, RMCMaterialSlots[Index].SlotName);
}
const auto LODConfig = RuntimeMesh->GetCopyOfConfiguration();
for (int32 LODIndex = 0; LODIndex < LODConfig.Num(); LODIndex++)
{
const auto& LOD = LODConfig[LODIndex];
// Raw mesh data we are filling in
FRawMesh RawMesh;
bool bUseHighPrecisionTangents = false;
bool bUseFullPrecisionUVs = false;
int32 MaxUVs = 1;
int32 VertexBase = 0;
for (const auto& SectionEntry : LOD.Sections)
{
const int32 SectionId = SectionEntry.Key;
const auto& Section = SectionEntry.Value;
// Here we need to direct query the provider the mesh is using
// We also go ahead and use high precision tangents/uvs so we don't loose
// quality passing through the build pipeline after quantizing it once
FRuntimeMeshRenderableMeshData MeshData(true, true, Section.NumTexCoords, true);
if (MeshProvider->GetSectionMeshForLOD(LODIndex, SectionEntry.Key, MeshData))
{
MaxUVs = FMath::Max<int32>(MaxUVs, Section.NumTexCoords);
// Fill out existing UV channels to start of this one
for (int32 Index = 0; Index < MaxUVs; Index++)
{
RawMesh.WedgeTexCoords[Index].SetNumZeroed(RawMesh.WedgeIndices.Num());
}
// Copy the vertex positions
int32 NumVertices = MeshData.Positions.Num();
for (int32 Index = 0; Index < NumVertices; Index++)
{
RawMesh.VertexPositions.Add(MeshData.Positions.GetPosition(Index));
}
// Copy wedges
int32 NumTris = MeshData.Triangles.Num();
for (int32 Index = 0; Index < NumTris; Index++)
{
int32 VertexIndex = MeshData.Triangles.GetVertexIndex(Index);
RawMesh.WedgeIndices.Add(VertexIndex + VertexBase);
FVector TangentX, TangentY, TangentZ;
MeshData.Tangents.GetTangents(VertexIndex, TangentX, TangentY, TangentZ);
RawMesh.WedgeTangentX.Add(TangentX);
RawMesh.WedgeTangentY.Add(TangentY);
RawMesh.WedgeTangentZ.Add(TangentZ);
for (int32 UVIndex = 0; UVIndex < Section.NumTexCoords; UVIndex++)
{
RawMesh.WedgeTexCoords[UVIndex].Add(MeshData.TexCoords.GetTexCoord(VertexIndex, UVIndex));
}
RawMesh.WedgeColors.Add(MeshData.Colors.GetColor(VertexIndex));
}
// Copy face info
for (int32 TriIdx = 0; TriIdx < NumTris / 3; TriIdx++)
{
// Set the face material
RawMesh.FaceMaterialIndices.Add(SectionId);
RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
}
// Update offset for creating one big index/vertex buffer
VertexBase += NumVertices;
}
}
// Fill out the UV channels to the same length as the indices
for (int32 Index = 0; Index < MaxUVs; Index++)
{
RawMesh.WedgeTexCoords[Index].SetNumZeroed(RawMesh.WedgeIndices.Num());
}
// If we got some valid data.
if (RawMesh.IsValid())
{
// Add source to new StaticMesh
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 23
FStaticMeshSourceModel* SrcModel = new (StaticMesh->GetSourceModels()) FStaticMeshSourceModel();
#else
FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
#endif
SrcModel->BuildSettings.bRecomputeNormals = false;
SrcModel->BuildSettings.bRecomputeTangents = false;
SrcModel->BuildSettings.bRemoveDegenerates = true;
SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = bUseHighPrecisionTangents;
SrcModel->BuildSettings.bUseFullPrecisionUVs = bUseFullPrecisionUVs;
SrcModel->BuildSettings.bGenerateLightmapUVs = true;
SrcModel->BuildSettings.SrcLightmapIndex = 0;
SrcModel->BuildSettings.DstLightmapIndex = 1;
SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh);
SrcModel->ScreenSize = LOD.Properties.ScreenSize;
// Set the materials used for this static mesh
int32 NumMaterials = StaticMesh->StaticMaterials.Num();
// Set up the SectionInfoMap to enable collision
for (int32 SectionIdx = 0; SectionIdx < NumMaterials; SectionIdx++)
{
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 23
FMeshSectionInfoMap& SectionInfoMap = StaticMesh->GetSectionInfoMap();
#else
FMeshSectionInfoMap& SectionInfoMap = StaticMesh->SectionInfoMap;
#endif
FMeshSectionInfo Info = SectionInfoMap.Get(LODIndex, SectionIdx);
Info.MaterialIndex = SectionIdx;
// TODO: Is this the correct way to handle this by just turning on collision in the top level LOD?
Info.bEnableCollision = LODIndex == 0;
SectionInfoMap.Set(LODIndex, SectionIdx, Info);
}
}
}
StaticMesh->StaticMaterials = Materials;
// Configure body setup for working collision.
StaticMesh->CreateBodySetup();
StaticMesh->BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple;
// Build mesh from source
StaticMesh->Build(false);
// Make package dirty.
StaticMesh->MarkPackageDirty();
StaticMesh->PostEditChange();
// Notify asset registry of new asset
FAssetRegistryModule::AssetCreated(StaticMesh);
}
}
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE