This commit is contained in:
FishOrBear
2021-06-30 17:11:29 +08:00
commit 55fe2a8d29
67 changed files with 20096 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,42 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "RuntimeGeometryUtils",
"Description": "",
"Category": "Other",
"CreatedBy": "Ryan Schmidt",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "RuntimeGeometryUtils",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "GeometryProcessing",
"Enabled": true
},
{
"Name": "MeshModelingToolset",
"Enabled": true
},
{
"Name": "ProceduralMeshComponent",
"Enabled": true
},
{
"Name": "MeshUtilities2",
"Enabled": true
}
]
}

View File

@@ -0,0 +1,441 @@
#include "DynamicMeshBaseActor.h"
#include "Generators/SphereGenerator.h"
#include "Generators/GridBoxMeshGenerator.h"
#include "MeshQueries.h"
#include "DynamicMesh3.h"
#include "MeshNormals.h"
#include "MeshTransforms.h"
#include "MeshSimplification.h"
#include "Operations/MeshBoolean.h"
#include "Implicit/Solidify.h"
#include "DynamicMeshOBJReader.h"
#include "MeshCardRepresentation2.h"
// Sets default values
ADynamicMeshBaseActor::ADynamicMeshBaseActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
AccumulatedTime = 0;
MeshAABBTree.SetMesh(&SourceMesh);
FastWinding = MakeUnique<TFastWindingTree<FDynamicMesh3>>(&MeshAABBTree, false);
}
void ADynamicMeshBaseActor::PostLoad()
{
Super::PostLoad();
OnMeshGenerationSettingsModified();
}
void ADynamicMeshBaseActor::PostActorCreated()
{
Super::PostActorCreated();
OnMeshGenerationSettingsModified();
}
// Called when the game starts or when spawned
void ADynamicMeshBaseActor::BeginPlay()
{
Super::BeginPlay();
AccumulatedTime = 0;
OnMeshGenerationSettingsModified();
}
// Called every frame
void ADynamicMeshBaseActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
AccumulatedTime += DeltaTime;
if (bRegenerateOnTick && SourceType == EDynamicMeshActorSourceType::Primitive)
{
OnMeshGenerationSettingsModified();
}
}
#if WITH_EDITOR
void ADynamicMeshBaseActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
OnMeshGenerationSettingsModified();
}
#endif
void ADynamicMeshBaseActor::EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc)
{
EditFunc(SourceMesh);
// update spatial data structures
if (bEnableSpatialQueries || bEnableInsideQueries)
{
MeshAABBTree.Build();
if (bEnableInsideQueries)
{
FastWinding->Build();
}
}
OnMeshEditedInternal();
}
void ADynamicMeshBaseActor::GetMeshCopy(FDynamicMesh3& MeshOut)
{
MeshOut = SourceMesh;
}
const FDynamicMesh3& ADynamicMeshBaseActor::GetMeshRef() const
{
return SourceMesh;
}
void ADynamicMeshBaseActor::OnMeshEditedInternal()
{
OnMeshModified.Broadcast(this);
}
void ADynamicMeshBaseActor::OnMeshGenerationSettingsModified()
{
EditMesh([this](FDynamicMesh3& MeshToUpdate)
{
RegenerateSourceMesh(MeshToUpdate);
});
}
void ADynamicMeshBaseActor::RegenerateSourceMesh(FDynamicMesh3& MeshOut)
{
if (SourceType == EDynamicMeshActorSourceType::Primitive)
{
double UseRadius = (this->MinimumRadius + this->VariableRadius)
+ (this->VariableRadius) * FMathd::Sin(PulseSpeed * AccumulatedTime);
// generate new mesh
if (this->PrimitiveType == EDynamicMeshActorPrimitiveType::Sphere)
{
FSphereGenerator SphereGen;
SphereGen.NumPhi = SphereGen.NumTheta = FMath::Clamp(this->TessellationLevel, 3, 50);
SphereGen.Radius = UseRadius;
MeshOut.Copy(&SphereGen.Generate());
}
else
{
FGridBoxMeshGenerator BoxGen;
int TessLevel = FMath::Clamp(this->TessellationLevel, 2, 50);
BoxGen.EdgeVertices = FIndex3i(TessLevel, TessLevel, TessLevel);
FVector3d BoxExtents = UseRadius * FVector3d::One();
BoxExtents.Z *= BoxDepthRatio;
BoxGen.Box = FOrientedBox3d(FVector3d::Zero(), BoxExtents);
MeshOut.Copy(&BoxGen.Generate());
}
}
else if (SourceType == EDynamicMeshActorSourceType::ImportedMesh)
{
FString UsePath = ImportPath;
if (FPaths::FileExists(UsePath) == false && FPaths::IsRelative(UsePath))
{
UsePath = FPaths::ProjectContentDir() + ImportPath;
}
MeshOut = FDynamicMesh3();
if (! RTGUtils::ReadOBJMesh(UsePath, MeshOut, true, true, true, bReverseOrientation))
{
UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *UsePath);
FSphereGenerator SphereGen;
SphereGen.NumPhi = SphereGen.NumTheta = 8;
SphereGen.Radius = this->MinimumRadius;
MeshOut.Copy(&SphereGen.Generate());
}
if (bCenterPivot)
{
MeshTransforms::Translate(MeshOut, -MeshOut.GetBounds().Center());
}
if (ImportScale != 1.0)
{
MeshTransforms::Scale(MeshOut, ImportScale * FVector3d::One(), FVector3d::Zero());
}
}
RecomputeNormals(MeshOut);
}
void ADynamicMeshBaseActor::RecomputeNormals(FDynamicMesh3& MeshOut)
{
if (this->NormalsMode == EDynamicMeshActorNormalsMode::PerVertexNormals)
{
MeshOut.EnableAttributes();
FMeshNormals::InitializeOverlayToPerVertexNormals(MeshOut.Attributes()->PrimaryNormals(), false);
}
else if (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals)
{
MeshOut.EnableAttributes();
FMeshNormals::InitializeOverlayToPerTriangleNormals(MeshOut.Attributes()->PrimaryNormals());
}
}
int ADynamicMeshBaseActor::GetTriangleCount()
{
return SourceMesh.TriangleCount();
}
float ADynamicMeshBaseActor::DistanceToPoint(FVector WorldPoint, FVector& NearestWorldPoint, int& NearestTriangle, FVector& TriBaryCoords)
{
NearestWorldPoint = WorldPoint;
NearestTriangle = -1;
if (bEnableSpatialQueries == false)
{
return TNumericLimits<float>::Max();
}
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
double NearDistSqr;
NearestTriangle = MeshAABBTree.FindNearestTriangle(LocalPoint, NearDistSqr);
if (NearestTriangle < 0)
{
return TNumericLimits<float>::Max();
}
FDistPoint3Triangle3d DistQuery = TMeshQueries<FDynamicMesh3>::TriangleDistance(SourceMesh, NearestTriangle, LocalPoint);
NearestWorldPoint = (FVector)ActorToWorld.TransformPosition(DistQuery.ClosestTrianglePoint);
TriBaryCoords = (FVector)DistQuery.TriangleBaryCoords;
return (float)FMathd::Sqrt(NearDistSqr);
}
FVector ADynamicMeshBaseActor::NearestPoint(FVector WorldPoint)
{
if (bEnableSpatialQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
return (FVector)ActorToWorld.TransformPosition(MeshAABBTree.FindNearestPoint(LocalPoint));
}
return WorldPoint;
}
bool ADynamicMeshBaseActor::ContainsPoint(FVector WorldPoint, float WindingThreshold)
{
if (bEnableInsideQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
return FastWinding->IsInside(LocalPoint, WindingThreshold);
}
return false;
}
bool ADynamicMeshBaseActor::IntersectRay(FVector RayOrigin, FVector RayDirection,
FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords,
float MaxDistance)
{
if (bEnableSpatialQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d WorldDirection(RayDirection);
WorldDirection.Normalize();
FRay3d LocalRay(ActorToWorld.InverseTransformPosition((FVector3d)RayOrigin),
ActorToWorld.InverseTransformNormal(WorldDirection));
IMeshSpatial::FQueryOptions QueryOptions;
if (MaxDistance > 0)
{
QueryOptions.MaxDistance = MaxDistance;
}
NearestTriangle = MeshAABBTree.FindNearestHitTriangle(LocalRay, QueryOptions);
if (SourceMesh.IsTriangle(NearestTriangle))
{
FIntrRay3Triangle3d IntrQuery = TMeshQueries<FDynamicMesh3>::TriangleIntersection(SourceMesh, NearestTriangle, LocalRay);
if (IntrQuery.IntersectionType == EIntersectionType::Point)
{
HitDistance = IntrQuery.RayParameter;
WorldHitPoint = (FVector)ActorToWorld.TransformPosition(LocalRay.PointAt(IntrQuery.RayParameter));
TriBaryCoords = (FVector)IntrQuery.TriangleBaryCoords;
return true;
}
}
}
return false;
}
void ADynamicMeshBaseActor::SubtractMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Subtraction);
}
void ADynamicMeshBaseActor::UnionWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Union);
}
void ADynamicMeshBaseActor::IntersectWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Intersection);
}
void ADynamicMeshBaseActor::BooleanWithMesh(ADynamicMeshBaseActor* OtherMeshActor, EDynamicMeshActorBooleanOperation Operation)
{
if (ensure(OtherMeshActor) == false) return;
FTransform3d ActorToWorld(GetActorTransform());
FTransform3d OtherToWorld(OtherMeshActor->GetActorTransform());
FDynamicMesh3 OtherMesh;
OtherMeshActor->GetMeshCopy(OtherMesh);
MeshTransforms::ApplyTransform(OtherMesh, OtherToWorld);
MeshTransforms::ApplyTransformInverse(OtherMesh, ActorToWorld);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
FDynamicMesh3 ResultMesh;
FMeshBoolean::EBooleanOp ApplyOp = FMeshBoolean::EBooleanOp::Union;
switch (Operation)
{
default:
break;
case EDynamicMeshActorBooleanOperation::Subtraction:
ApplyOp = FMeshBoolean::EBooleanOp::Difference;
break;
case EDynamicMeshActorBooleanOperation::Intersection:
ApplyOp = FMeshBoolean::EBooleanOp::Intersect;
break;
}
FMeshBoolean Boolean(
&MeshToUpdate, FTransform3d::Identity(),
&OtherMesh, FTransform3d::Identity(),
&ResultMesh,
ApplyOp);
Boolean.bPutResultInInputSpace = true;
bool bOK = Boolean.Compute();
if (!bOK)
{
// fill holes
}
RecomputeNormals(ResultMesh);
MeshToUpdate = MoveTemp(ResultMesh);
});
}
bool ADynamicMeshBaseActor::ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals)
{
FDynamicMesh3 ImportedMesh;
if (!RTGUtils::ReadOBJMesh(Path, ImportedMesh, true, true, true, bFlipOrientation))
{
UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *Path);
return false;
}
if (bRecomputeNormals)
{
RecomputeNormals(ImportedMesh);
}
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(ImportedMesh);
});
return true;
}
void ADynamicMeshBaseActor::CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals)
{
if (! ensure(OtherMesh)) return;
// the part where we generate a new mesh
FDynamicMesh3 TmpMesh;
OtherMesh->GetMeshCopy(TmpMesh);
// apply our normals setting
if (bRecomputeNormals)
{
RecomputeNormals(TmpMesh);
}
// update the mesh
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(TmpMesh);
});
}
void ADynamicMeshBaseActor::SolidifyMesh(int VoxelResolution, float WindingThreshold)
{
if (MeshAABBTree.IsValid() == false)
{
MeshAABBTree.Build();
}
if (FastWinding->IsBuilt() == false)
{
FastWinding->Build();
}
// ugh workaround for bug
FDynamicMesh3 CompactMesh;
CompactMesh.CompactCopy(SourceMesh, false, false, false, false);
FDynamicMeshAABBTree3 AABBTree(&CompactMesh, true);
TFastWindingTree<FDynamicMesh3> Winding(&AABBTree, true);
double ExtendBounds = 2.0;
//TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&SourceMesh, &MeshAABBTree, FastWinding.Get());
//SolidifyCalc.SetCellSizeAndExtendBounds(MeshAABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&CompactMesh, &AABBTree, &Winding);
SolidifyCalc.SetCellSizeAndExtendBounds(AABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
SolidifyCalc.WindingThreshold = WindingThreshold;
SolidifyCalc.SurfaceSearchSteps = 5;
SolidifyCalc.bSolidAtBoundaries = true;
SolidifyCalc.ExtendBounds = ExtendBounds;
FDynamicMesh3 SolidMesh(&SolidifyCalc.Generate());
SolidMesh.EnableAttributes();
RecomputeNormals(SolidMesh);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(SolidMesh);
});
}
void ADynamicMeshBaseActor::SimplifyMeshToTriCount(int32 TargetTriangleCount)
{
TargetTriangleCount = FMath::Max(1, TargetTriangleCount);
if (TargetTriangleCount >= SourceMesh.TriangleCount()) return;
// make compacted copy because it seems to change the results?
FDynamicMesh3 SimplifyMesh;
SimplifyMesh.CompactCopy(SourceMesh, false, false, false, false);
SimplifyMesh.EnableTriangleGroups(); // workaround for failing check()
FQEMSimplification Simplifier(&SimplifyMesh);
Simplifier.SimplifyToTriangleCount(TargetTriangleCount);
SimplifyMesh.EnableAttributes();
RecomputeNormals(SimplifyMesh);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate.CompactCopy(SimplifyMesh);
});
}

View File

@@ -0,0 +1,141 @@
#include "DynamicMeshOBJReader.h"
#include "DynamicMeshAttributeSet.h"
#include "tinyobj/tiny_obj_loader.h"
bool RTGUtils::ReadOBJMesh(
const FString& Path,
FDynamicMesh3& MeshOut,
bool bNormals,
bool bTexCoords,
bool bVertexColors,
bool bReverseOrientation)
{
std::string inputfile(TCHAR_TO_UTF8(*Path));
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
if (!warn.empty())
{
UE_LOG(LogTemp, Display, TEXT("%s"), warn.c_str());
}
if (!err.empty())
{
UE_LOG(LogTemp, Display, TEXT("%s"), err.c_str());
}
if (!ret)
{
return false;
}
// append vertices
for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
{
tinyobj::real_t vx = attrib.vertices[3 * vi + 0];
tinyobj::real_t vy = attrib.vertices[3 * vi + 1];
tinyobj::real_t vz = attrib.vertices[3 * vi + 2];
MeshOut.AppendVertex(FVector3d(vx, vy, vz));
}
if (bVertexColors)
{
MeshOut.EnableVertexColors(FVector3f::Zero());
for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
{
tinyobj::real_t r = attrib.colors[3 * vi + 0];
tinyobj::real_t g = attrib.colors[3 * vi + 1];
tinyobj::real_t b = attrib.colors[3 * vi + 2];
MeshOut.SetVertexColor(vi, FVector3f((float)r, (float)g, (float)b));
}
}
if (bNormals || bTexCoords)
{
MeshOut.EnableAttributes();
}
FDynamicMeshNormalOverlay* Normals = (bNormals) ? MeshOut.Attributes()->PrimaryNormals() : nullptr;
FDynamicMeshUVOverlay* UVs = (bTexCoords) ? MeshOut.Attributes()->PrimaryUV() : nullptr;
if (Normals)
{
for (size_t ni = 0; ni < attrib.normals.size() / 3; ++ni)
{
tinyobj::real_t nx = attrib.normals[3 * ni + 0];
tinyobj::real_t ny = attrib.normals[3 * ni + 1];
tinyobj::real_t nz = attrib.normals[3 * ni + 2];
Normals->AppendElement(FVector3f((float)nx, (float)ny, (float)nz));
}
}
if (UVs)
{
for (size_t ti = 0; ti < attrib.texcoords.size() / 2; ++ti)
{
tinyobj::real_t tx = attrib.texcoords[2 * ti + 0];
tinyobj::real_t ty = attrib.texcoords[2 * ti + 1];
UVs->AppendElement(FVector2f((float)tx, (float)ty));
}
}
// append faces as triangles
for (size_t s = 0; s < shapes.size(); s++)
{
// Loop over shapes
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
{
// Loop over faces(polygon)
int fv = shapes[s].mesh.num_face_vertices[f];
TArray<FIndex3i> Triangles;
for (size_t v = 1; v < fv - 1; v++)
{
Triangles.Add(FIndex3i(0, v, v + 1));
}
int32 NumTris = Triangles.Num();
for (int32 ti = 0; ti < NumTris; ++ti)
{
FIndex3i TriVerts = Triangles[ti];
tinyobj::index_t idx0 = shapes[s].mesh.indices[index_offset + TriVerts.A];
tinyobj::index_t idx1 = shapes[s].mesh.indices[index_offset + TriVerts.B];
tinyobj::index_t idx2 = shapes[s].mesh.indices[index_offset + TriVerts.C];
int32 tid = MeshOut.AppendTriangle(idx0.vertex_index, idx1.vertex_index, idx2.vertex_index);
if (Normals && Normals->IsElement(idx0.normal_index) && Normals->IsElement(idx1.normal_index) && Normals->IsElement(idx2.normal_index))
{
Normals->SetTriangle(tid, FIndex3i(idx0.normal_index, idx1.normal_index, idx2.normal_index));
}
if (UVs && UVs->IsElement(idx0.texcoord_index) && UVs->IsElement(idx1.texcoord_index) && UVs->IsElement(idx2.texcoord_index))
{
UVs->SetTriangle(tid, FIndex3i(idx0.texcoord_index, idx1.texcoord_index, idx2.texcoord_index));
}
}
index_offset += fv;
// per-face material
//shapes[s].mesh.material_ids[f];
}
}
if (bReverseOrientation)
{
MeshOut.ReverseOrientation();
}
return true;
}

View File

@@ -0,0 +1,191 @@
#include "DynamicMeshOBJWriter.h"
#include "DynamicMeshAttributeSet.h"
#include "DynamicMeshEditor.h"
#include <fstream>
class FDynamicMeshOBJWriter
{
public:
std::ofstream FileOut;
TFunction<bool(const FString&)> OpenFile = [this](const FString& Path) { FileOut.open(*Path, std::ofstream::out | std::ofstream::trunc); return !!FileOut; };
TFunction<void()> CloseFile = [this]() { FileOut.close(); };
TFunction<void(const TCHAR*)> WriteLine = [this](const TCHAR* Line) { FileOut << TCHAR_TO_ANSI(Line) << std::endl; };
bool Write(const char* OutputPath, const FDynamicMesh3& Mesh)
{
if (!OpenFile(OutputPath))
{
return false;
}
int32 NumVertices = Mesh.VertexCount();
for (int32 vi = 0; vi < NumVertices; ++vi)
{
FVector3d Pos = Mesh.GetVertex(vi);
WriteLine(*FString::Printf(TEXT("v %f %f %f"), Pos.X, Pos.Y, Pos.Z));
}
int32 NumUVs = 0;
const FDynamicMeshUVOverlay* UVs = nullptr;
if (Mesh.Attributes() && Mesh.Attributes()->PrimaryUV())
{
UVs = Mesh.Attributes()->PrimaryUV();
NumUVs = UVs->ElementCount();
for (int32 ui = 0; ui < NumUVs; ++ui)
{
FVector2f UV = UVs->GetElement(ui);
WriteLine(*FString::Printf(TEXT("vt %f %f"), UV.X, UV.Y));
}
}
int32 NumNormals = 0;
const FDynamicMeshNormalOverlay* Normals = nullptr;
if (Mesh.Attributes() && Mesh.Attributes()->PrimaryNormals())
{
Normals = Mesh.Attributes()->PrimaryNormals();
NumNormals = Normals->ElementCount();
for (int32 ni = 0; ni < NumNormals; ++ni)
{
FVector3f Normal = Normals->GetElement(ni);
WriteLine(*FString::Printf(TEXT("vn %f %f %f"), Normal.X, Normal.Y, Normal.Z));
}
}
struct FMeshTri
{
int32 Index;
int32 Group;
};
TSet<int32> AllGroupIDs;
TArray<FMeshTri> Triangles;
int32 NumTriangles = Mesh.TriangleCount();
for (int32 ti = 0; ti < NumTriangles; ++ti)
{
if (Mesh.IsTriangle(ti))
{
int32 GroupID = Mesh.GetTriangleGroup(ti);
AllGroupIDs.Add(GroupID);
Triangles.Add({ ti, GroupID });
}
}
bool bHaveGroups = AllGroupIDs.Num() > 1;
Triangles.StableSort([](const FMeshTri& Tri0, const FMeshTri& Tri1)
{
return Tri0.Group < Tri1.Group;
});
int32 CurGroupID = -99999;
for (FMeshTri MeshTri : Triangles)
{
if (bHaveGroups && MeshTri.Group != CurGroupID)
{
WriteLine(*FString::Printf(TEXT("g %d"), MeshTri.Group));
CurGroupID = MeshTri.Group;
}
int32 ti = MeshTri.Index;
FIndex3i TriVertices = Mesh.GetTriangle(ti);
FIndex3i TriUVs = (NumUVs > 0) ? UVs->GetTriangle(ti) : FIndex3i::Invalid();
FIndex3i TriNormals = (NumNormals > 0) ? Normals->GetTriangle(ti) : FIndex3i::Invalid();
bool bHaveUV = (NumUVs != 0) && UVs->IsSetTriangle(ti);
bool bHaveNormal = (NumNormals != 0) && Normals->IsSetTriangle(ti);
if (bHaveUV && bHaveNormal)
{
WriteLine(*FString::Printf(TEXT("f %d/%d/%d %d/%d/%d %d/%d/%d"),
TriVertices.A + 1, TriUVs.A + 1, TriNormals.A + 1,
TriVertices.B + 1, TriUVs.B + 1, TriNormals.B + 1,
TriVertices.C + 1, TriUVs.C + 1, TriNormals.C + 1));
}
else if (bHaveUV)
{
WriteLine(*FString::Printf(TEXT("f %d/%d %d/%d %d/%d"),
TriVertices.A + 1, TriUVs.A + 1, TriVertices.B + 1, TriUVs.B + 1, TriVertices.C + 1, TriUVs.C + 1));
}
else if (bHaveNormal)
{
WriteLine(*FString::Printf(TEXT("f %d//%d %d//%d %d//%d"),
TriVertices.A + 1, TriNormals.A + 1, TriVertices.B + 1, TriNormals.B + 1, TriVertices.C + 1, TriNormals.C + 1));
}
else
{
WriteLine(*FString::Printf(TEXT("f %d %d %d"), TriVertices.A + 1, TriVertices.B + 1, TriVertices.C + 1));
}
}
CloseFile();
return true;
}
};
bool RTGUtils::WriteOBJMesh(
const FString& OutputPath,
const FDynamicMesh3& Mesh,
bool bReverseOrientation)
{
const FDynamicMesh3* WriteMesh = &Mesh;
FDynamicMesh3 CopyMesh;
if (bReverseOrientation)
{
CopyMesh = Mesh;
CopyMesh.ReverseOrientation();
WriteMesh = &CopyMesh;
}
FDynamicMeshOBJWriter Writer;
std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
return Writer.Write(OutputFilePath.c_str(), *WriteMesh);
}
bool RTGUtils::WriteOBJMeshes(
const FString& OutputPath,
const TArray<FDynamicMesh3>& Meshes,
bool bReverseOrientation)
{
FDynamicMesh3 CombinedMesh;
FDynamicMeshEditor Editor(&CombinedMesh);
for (const FDynamicMesh3& Mesh : Meshes)
{
if (Mesh.HasTriangleGroups())
{
CombinedMesh.EnableTriangleGroups();
}
if (Mesh.HasAttributes())
{
CombinedMesh.EnableAttributes();
}
FMeshIndexMappings Mappings;
Editor.AppendMesh(&Mesh, Mappings);
}
if (bReverseOrientation)
{
CombinedMesh.ReverseOrientation();
}
FDynamicMeshOBJWriter Writer;
std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
return Writer.Write(OutputFilePath.c_str(), CombinedMesh);
}

View File

@@ -0,0 +1,55 @@
#include "DynamicPMCActor.h"
#include "MeshComponentRuntimeUtils.h"
#include "DynamicMesh3.h"
// Sets default values
ADynamicPMCActor::ADynamicPMCActor()
{
MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("Mesh"), false);
SetRootComponent(MeshComponent);
}
// Called when the game starts or when spawned
void ADynamicPMCActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADynamicPMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicPMCActor::OnMeshEditedInternal()
{
UpdatePMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicPMCActor::UpdatePMCMesh()
{
if (MeshComponent)
{
bool bUseFaceNormals = (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals);
bool bUseUV0 = true;
bool bUseVertexColors = false;
bool bGenerateSectionCollision = false;
if (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimple
|| this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync)
{
bGenerateSectionCollision = true;
MeshComponent->bUseAsyncCooking = (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync);
MeshComponent->bUseComplexAsSimpleCollision = true;
}
RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(MeshComponent, &SourceMesh, bUseFaceNormals, bUseUV0, bUseVertexColors, bGenerateSectionCollision);
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
MeshComponent->SetMaterial(0, UseMaterial);
}
}

View File

@@ -0,0 +1,45 @@
#include "DynamicSDMCActor.h"
#include "DynamicMesh3.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
// Sets default values
ADynamicSDMCActor::ADynamicSDMCActor()
{
MeshComponent = CreateDefaultSubobject<USimpleDynamicMeshComponent>(TEXT("MeshComponent"), false);
SetRootComponent(MeshComponent);
}
// Called when the game starts or when spawned
void ADynamicSDMCActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADynamicSDMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicSDMCActor::OnMeshEditedInternal()
{
UpdateSDMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicSDMCActor::UpdateSDMCMesh()
{
if (MeshComponent)
{
*(MeshComponent->GetMesh()) = SourceMesh;
MeshComponent->NotifyMeshUpdated();
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
MeshComponent->SetMaterial(0, UseMaterial);
}
}

View File

@@ -0,0 +1,63 @@
#include "DynamicSMCActor.h"
#include "MeshComponentRuntimeUtils.h"
// Sets default values
ADynamicSMCActor::ADynamicSMCActor()
{
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"), false);
SetRootComponent(MeshComponent);
StaticMesh = nullptr;
}
// Called when the game starts or when spawned
void ADynamicSMCActor::BeginPlay()
{
StaticMesh = nullptr;
Super::BeginPlay();
}
void ADynamicSMCActor::PostLoad()
{
StaticMesh = nullptr;
Super::PostLoad();
}
void ADynamicSMCActor::PostActorCreated()
{
StaticMesh = nullptr;
Super::PostActorCreated();
}
// Called every frame
void ADynamicSMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicSMCActor::OnMeshEditedInternal()
{
UpdateSMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicSMCActor::UpdateSMCMesh()
{
if (StaticMesh == nullptr)
{
StaticMesh = NewObject<UStaticMesh>();
MeshComponent->SetStaticMesh(StaticMesh);
// add one material slot
StaticMesh->GetStaticMaterials().Add(FStaticMaterial());
}
if (MeshComponent)
{
auto MTL = LoadObject<UMaterialInterface>(nullptr,TEXT("Material'/Game/baise.baise'"));
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : MTL;
MeshComponent->SetMaterial(0, UseMaterial);
RTGUtils::UpdateStaticMeshFromDynamicMesh(StaticMesh, &SourceMesh);
}
}

View File

@@ -0,0 +1,147 @@
#include "MeshComponentRuntimeUtils.h"
#include "DynamicMeshAttributeSet.h"
#include "MeshNormals.h"
#include "DynamicMeshToMeshDescription.h"
#include "StaticMeshAttributes.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
void RTGUtils::UpdateStaticMeshFromDynamicMesh(
UStaticMesh* StaticMesh,
const FDynamicMesh3* Mesh)
{
FMeshDescription MeshDescription;
FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
StaticMeshAttributes.Register();
FDynamicMeshToMeshDescription Converter;
Converter.Convert(Mesh, MeshDescription);
// todo: vertex color support
//UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Component);
//FName MaterialSlotName = StaticMesh->AddMaterial(MyMaterial);
// Build the static mesh render data, one FMeshDescription* per LOD.
TArray<const FMeshDescription*> MeshDescriptionPtrs;
MeshDescriptionPtrs.Emplace(&MeshDescription);
UStaticMesh::FBuildMeshDescriptionsParams Params;
Params.bFastBuild = true;
StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs,Params);
//在编辑器模式下,也使用自定义的距离场构建和MeshCard构建
BuildMeshDistanceField(StaticMesh);
}
void RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(
UProceduralMeshComponent* Component,
const FDynamicMesh3* Mesh,
bool bUseFaceNormals,
bool bInitializeUV0,
bool bInitializePerVertexColors,
bool bCreateCollision)
{
Component->ClearAllMeshSections();
int32 NumTriangles = Mesh->TriangleCount();
int32 NumVertices = NumTriangles * 3;
TArray<FVector> Vertices, Normals;
Vertices.SetNumUninitialized(NumVertices);
Normals.SetNumUninitialized(NumVertices);
FMeshNormals PerVertexNormals(Mesh);
bool bUsePerVertexNormals = false;
const FDynamicMeshNormalOverlay* NormalOverlay = nullptr;
if (Mesh->HasAttributes() == false && bUseFaceNormals == false)
{
PerVertexNormals.ComputeVertexNormals();
bUsePerVertexNormals = true;
}
else if (Mesh->HasAttributes())
{
NormalOverlay = Mesh->Attributes()->PrimaryNormals();
}
const FDynamicMeshUVOverlay* UVOverlay = (Mesh->HasAttributes()) ? Mesh->Attributes()->PrimaryUV() : nullptr;
TArray<FVector2D> UV0;
if (UVOverlay && bInitializeUV0)
{
UV0.SetNum(NumVertices);
}
TArray<FLinearColor> VtxColors;
bool bUsePerVertexColors = false;
if (bInitializePerVertexColors && Mesh->HasVertexColors())
{
VtxColors.SetNum(NumVertices);
bUsePerVertexColors = true;
}
TArray<FProcMeshTangent> Tangents; // not supporting this for now
TArray<int32> Triangles;
Triangles.SetNumUninitialized(NumTriangles * 3);
FVector3d Position[3];
FVector3f Normal[3];
FVector2f UV[3];
int32 BufferIndex = 0;
for (int32 tid : Mesh->TriangleIndicesItr())
{
int32 k = 3 * (BufferIndex++);
FIndex3i TriVerts = Mesh->GetTriangle(tid);
Mesh->GetTriVertices(tid, Position[0], Position[1], Position[2]);
Vertices[k] = (FVector)Position[0];
Vertices[k + 1] = (FVector)Position[1];
Vertices[k + 2] = (FVector)Position[2];
if (bUsePerVertexNormals)
{
Normals[k] = (FVector)PerVertexNormals[TriVerts.A];
Normals[k + 1] = (FVector)PerVertexNormals[TriVerts.B];
Normals[k + 2] = (FVector)PerVertexNormals[TriVerts.C];
}
else if (NormalOverlay != nullptr && bUseFaceNormals == false)
{
NormalOverlay->GetTriElements(tid, Normal[0], Normal[1], Normal[2]);
Normals[k] = (FVector)Normal[0];
Normals[k + 1] = (FVector)Normal[1];
Normals[k + 2] = (FVector)Normal[2];
}
else
{
FVector3d TriNormal = Mesh->GetTriNormal(tid);
Normals[k] = (FVector)TriNormal;
Normals[k + 1] = (FVector)TriNormal;
Normals[k + 2] = (FVector)TriNormal;
}
if (UVOverlay != nullptr && UVOverlay->IsSetTriangle(tid))
{
UVOverlay->GetTriElements(tid, UV[0], UV[1], UV[2]);
UV0[k] = (FVector2D)UV[0];
UV0[k + 1] = (FVector2D)UV[1];
UV0[k + 2] = (FVector2D)UV[2];
}
if (bUsePerVertexColors)
{
VtxColors[k] = (FLinearColor)Mesh->GetVertexColor(TriVerts.A);
VtxColors[k + 1] = (FLinearColor)Mesh->GetVertexColor(TriVerts.B);
VtxColors[k + 2] = (FLinearColor)Mesh->GetVertexColor(TriVerts.C);
}
Triangles[k] = k;
Triangles[k + 1] = k + 1;
Triangles[k + 2] = k + 2;
}
Component->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VtxColors, Tangents, bCreateCollision);
}

View File

@@ -0,0 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "RuntimeGeometryUtilsModule.h"
#define LOCTEXT_NAMESPACE "FRuntimeGeometryUtilsModule"
void FRuntimeGeometryUtilsModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FRuntimeGeometryUtilsModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRuntimeGeometryUtilsModule, RuntimeGeometryUtils)

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2019 Syoyo Fujita and many contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,334 @@
# tinyobjloader
[![Build Status](https://travis-ci.org/tinyobjloader/tinyobjloader.svg?branch=master)](https://travis-ci.org/tinyobjloader/tinyobjloader)
[![AZ Build Status](https://dev.azure.com/tinyobjloader/tinyobjloader/_apis/build/status/tinyobjloader.tinyobjloader?branchName=master)](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master)
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/m6wfkvket7gth8wn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master)
[![AUR version](https://img.shields.io/aur/version/tinyobjloader?logo=arch-linux)](https://aur.archlinux.org/packages/tinyobjloader)
[![Download](https://api.bintray.com/packages/conan/conan-center/tinyobjloader%3A_/images/download.svg)](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion) (not recommended)
Tiny but powerful single file wavefront obj loader written in C++03. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time.
`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-)
If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c .
Notice!
-------
We have released new version v1.0.0 on 20 Aug, 2016.
Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x
## What's new
* 19 Feb, 2020 : The repository has been moved to https://github.com/tinyobjloader/tinyobjloader !
* 18 May, 2019 : Python binding!(See `python` folder. Also see https://pypi.org/project/tinyobjloader/)
* 14 Apr, 2019 : Bump version v2.0.0 rc0. New C++ API and python bindings!(1.x API still exists for backward compatibility)
* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API!
## Requirements
* C++03 compiler
### Old version
Previous old version is available in `v0.9.x` branch.
## Example
![Rungholt](images/rungholt.jpg)
tinyobjloader can successfully load 6M triangles Rungholt scene.
http://casual-effects.com/data/index.html
![](images/sanmugel.png)
* [examples/viewer/](examples/viewer) OpenGL .obj viewer
* [examples/callback_api/](examples/callback_api/) Callback API example
* [examples/voxelize/](examples/voxelize/) Voxelizer example
## Use case
TinyObjLoader is successfully used in ...
### New version(v1.0.x)
* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma
* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models
* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master
* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook
* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox
* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake
* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer
* glslViewer: https://github.com/patriciogonzalezvivo/glslViewer
* Lighthouse2: https://github.com/jbikker/lighthouse2
* rayrender(an open source R package for raytracing scenes in created in R): https://github.com/tylermorganwall/rayrender
* liblava - A modern C++ and easy-to-use framework for the Vulkan API. [MIT]: https://github.com/liblava/liblava
* rtxON - Simple Vulkan raytracing tutorials https://github.com/iOrange/rtxON
* metal-ray-tracer - Writing ray-tracer using Metal Performance Shaders https://github.com/sergeyreznik/metal-ray-tracer https://sergeyreznik.github.io/metal-ray-tracer/index.html
* Your project here! (Letting us know via github issue is welcome!)
### Old version(v0.9.x)
* bullet3 https://github.com/erwincoumans/bullet3
* pbrt-v2 https://github.com/mmp/pbrt-v2
* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01
* mallie https://lighttransport.github.io/mallie
* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/
* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf
* Awesome Bump http://awesomebump.besaba.com/about/
* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront
* pbrt-v3 https://github.com/mmp/pbrt-v3
* cocos2d-x https://github.com/cocos2d/cocos2d-x/
* Android Vulkan demo https://github.com/SaschaWillems/Vulkan
* voxelizer https://github.com/karimnaaji/voxelizer
* Probulator https://github.com/kayru/Probulator
* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking
* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK
* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg
* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl
* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/
* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf
* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf
* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/
## Features
* Group(parse multiple group name)
* Vertex
* Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender)
* Texcoord
* Normal
* Material
* Unknown material attributes are returned as key-value(value is string) map.
* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification)
* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
* Callback API for custom loading.
* Double precision support(for HPC application).
* Smoothing group
* Python binding : See `python` folder.
* Precompiled binary(manylinux1-x86_64 only) is hosted at pypi https://pypi.org/project/tinyobjloader/)
### Primitives
* [x] face(`f`)
* [x] lines(`l`)
* [ ] points(`p`)
* [ ] curve
* [ ] 2D curve
* [ ] surface.
* [ ] Free form curve/surfaces
## TODO
* [ ] Fix obj_sticker example.
* [ ] More unit test codes.
* [x] Texture options
## License
TinyObjLoader is licensed under MIT license.
### Third party licenses.
* pybind11 : BSD-style license.
## Usage
### Installation
One option is to simply copy the header file into your project and to make sure that `TINYOBJLOADER_IMPLEMENTATION` is defined exactly once.
Tinyobjlaoder is also available as a [conan package](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion). Conan integrates with many build systems and lets you avoid manual dependency installation. Their [documentation](https://docs.conan.io/en/latest/getting_started.html) is a great starting point.
### Building tinyobjloader - Using vcpkg
You can download and install tinyobjloader using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install tinyobjloader
The tinyobjloader port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
### Data format
`attrib_t` contains single and linear array of vertex data(position, normal and texcoord).
```
attrib_t::vertices => 3 floats per vertex
v[0] v[1] v[2] v[3] v[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::normals => 3 floats per vertex
n[0] n[1] n[2] n[3] n[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::texcoords => 2 floats per vertex
t[0] t[1] t[2] t[3] t[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| u | v | u | v | u | v | u | v | .... | u | v |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::colors => 3 floats per vertex(vertex color. optional)
c[0] c[1] c[2] c[3] c[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
```
Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`.
See `loader_example.cc` for more details.
```
mesh_t::indices => array of vertex indices.
+----+----+----+----+----+----+----+----+----+----+ +--------+
| i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) |
+----+----+----+----+----+----+----+----+----+----+ +--------+
Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords.
mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons).
+---+---+---+ +---+
| 3 | 4 | 3 | ...... | 3 |
+---+---+---+ +---+
| | | |
| | | +-----------------------------------------+
| | | |
| | +------------------------------+ |
| | | |
| +------------------+ | |
| | | |
|/ |/ |/ |/
mesh_t::indices
| face[0] | face[1] | face[2] | | face[n-1] |
+----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
| i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) |
+----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
```
Note that when `triangulate` flag is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle).
### float data type
TinyObjLoader now use `real_t` for floating point data type.
Default is `float(32bit)`.
You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define.
#### Example code
```c++
#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc
#include "tiny_obj_loader.h"
std::string inputfile = "cornell_box.obj";
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
if (!warn.empty()) {
std::cout << warn << std::endl;
}
if (!err.empty()) {
std::cerr << err << std::endl;
}
if (!ret) {
exit(1);
}
// Loop over shapes
for (size_t s = 0; s < shapes.size(); s++) {
// Loop over faces(polygon)
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
int fv = shapes[s].mesh.num_face_vertices[f];
// Loop over vertices in the face.
for (size_t v = 0; v < fv; v++) {
// access to vertex
tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
tinyobj::real_t vx = attrib.vertices[3*idx.vertex_index+0];
tinyobj::real_t vy = attrib.vertices[3*idx.vertex_index+1];
tinyobj::real_t vz = attrib.vertices[3*idx.vertex_index+2];
tinyobj::real_t nx = attrib.normals[3*idx.normal_index+0];
tinyobj::real_t ny = attrib.normals[3*idx.normal_index+1];
tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2];
tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0];
tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1];
// Optional: vertex colors
// tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
// tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
// tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];
}
index_offset += fv;
// per-face material
shapes[s].mesh.material_ids[f];
}
}
```
## Optimized loader
Optimized multi-threaded .obj loader is available at `experimental/` directory.
If you want absolute performance to load .obj data, this optimized loader will fit your purpose.
Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data.
Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz).
* Rungholt scene(6M triangles)
* old version(v0.9.x): 15500 msecs.
* baseline(v1.0.x): 6800 msecs(2.3x faster than old version)
* optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline)
## Python binding
### CI + PyPI upload
cibuildwheels + twine upload for each git tagging event is handled in Azure Pipeline.
#### How to bump version(For developer)
* Bump version in CMakeLists.txt
* Update version in `python/setup.py`
* Commit with tag name starging with `v`(e.g. `v2.1.0`)
* `git push --tags`
* cibuildwheels + pypi upload(through twine) will be automatically triggered in Azure Pipeline.
## Tests
Unit tests are provided in `tests` directory. See `tests/README.md` for details.

View File

@@ -0,0 +1,2 @@
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"

View File

@@ -0,0 +1,373 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DynamicMesh3.h"
#include "DynamicMeshAABBTree3.h"
#include "Spatial/FastWinding.h"
#include "DynamicMeshBaseActor.generated.h"
/**
* Type of Normals computation used by ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorNormalsMode : uint8
{
SplitNormals = 0,
PerVertexNormals = 1,
FaceNormals = 2
};
/**
* Source of mesh used to initialize ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorSourceType : uint8
{
Primitive,
ImportedMesh
};
/**
* Geometric primitive types supported of by ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorPrimitiveType : uint8
{
Sphere,
Box
};
/**
* Boolean operation types supported of by ADynamicMeshBaseActor
*/
UENUM(BlueprintType)
enum class EDynamicMeshActorBooleanOperation : uint8
{
Union,
Subtraction,
Intersection
};
UENUM(BlueprintType)
enum class EDynamicMeshActorCollisionMode : uint8
{
NoCollision,
ComplexAsSimple,
ComplexAsSimpleAsync
};
/**
* ADynamicMeshBaseActor is a base class for Actors that support being
* rebuilt in-game after mesh editing operations. The base Actor itself
* does not have any Components, it should be used via one of the
* DynamicPMCActor, DynamicSMCActor, or DynamicSDMCActor subclasses.
*
* ADynamicMeshBaseActor provides a FDynamicMesh3 "Source Mesh", which can
* be modified via lambdas passed to the EditMesh() function, which will
* then cause necessary updates to happen to the implementing Components.
* An AABBTree and FastWindingTree can optionally be enabled with the
* bEnableSpatialQueries and bEnableInsideQueries flags.
*
* When Spatial queries are enabled, a set of UFunctions DistanceToPoint(),
* NearestPoint(), ContainsPoint(), and IntersectRay() are available via Blueprints
* on the relevant Actor. These functions *do not* depend on the UE4 Physics
* system to work.
*
* A small set of mesh modification UFunctions are also available via Blueprints,
* including BooleanWithMesh(), SolidifyMesh(), SimplifyMeshToTriCount(), and
* CopyFromMesh().
*
* Meshes can be read from OBJ files either using the ImportedMesh type for
* the SourceType property, or by calling the ImportMesh() UFunction from a Blueprint.
* Note that calling this in a Construction Script will be problematic in the Editor
* as the OBJ will be re-read any time the Actor is modified (including translated/rotated).
*
* Any Material set on the subclass Components will be overriden by the Material property.
*
*
*/
UCLASS(Abstract)
class RUNTIMEGEOMETRYUTILS_API ADynamicMeshBaseActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicMeshBaseActor();
public:
/** Type of mesh used to initialize this Actor - either a generated mesh Primitive or an Imported OBJ file */
UPROPERTY(EditAnywhere, Category = MeshOptions)
EDynamicMeshActorSourceType SourceType = EDynamicMeshActorSourceType::Primitive;
/** Type of normals computed for the Mesh */
UPROPERTY(EditAnywhere, Category = MeshOptions)
EDynamicMeshActorNormalsMode NormalsMode = EDynamicMeshActorNormalsMode::SplitNormals;
/** Material assigned to child Components created by subclasses */
UPROPERTY(EditAnywhere, Category = MaterialOptions)
UMaterialInterface* Material;
/** If true, mesh will be regenerated or re-imported on tick. Can be useful for prototyping procedural animation, but not the most efficient way to do it */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
bool bRegenerateOnTick = false;
//
// Parameters for SourceType = Imported
//
/** Path to OBJ file read to initialize mesh in SourceType=Imported mode */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
FString ImportPath;
/** Whether the imported mesh should have it's triangles reversed (commonly required for meshes authored in DCC tools) */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
bool bReverseOrientation = true;
/** If true the imported mesh will be recentered around the origin */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
bool bCenterPivot = true;
/** Uniform scaling applied to the imported mesh (baked into the mesh vertices, not the actor Transform) */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
float ImportScale = 1.0;
//
// Parameters for SourceType = Primitive
//
/** Type of generated mesh primitive */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
EDynamicMeshActorPrimitiveType PrimitiveType = EDynamicMeshActorPrimitiveType::Box;
/** Triangle density of generated primitive */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, UIMax = 50, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
int TessellationLevel = 8;
/** Radius of generated sphere / Width of generated box */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float MinimumRadius = 50;
/** Multiplier on MinimumRadius used to define box depth (ie dimension on Z axis) */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0.01, UIMax = 1.0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive && PrimitiveType == EDynamicMeshActorPrimitiveType::Box", EditConditionHides))
float BoxDepthRatio = 1.0;
/** A random value in range [-VariableRadius, VariableRadius] is added to MinimumRadius */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float VariableRadius = 0;
/** Speed of variation of VariableRadius, only has an effect when bRegenerateOnTick is true */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float PulseSpeed = 3.0;
//
// ADynamicMeshBaseActor API
//
public:
/**
* Call EditMesh() to safely modify the SourceMesh owned by this Actor.
* Your EditFunc will be called with the Current SourceMesh as argument,
* and you are expected to pass back the new/modified version.
* (If you are generating an entirely new mesh, MoveTemp can be used to do this without a copy)
*/
virtual void EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc);
/**
* Get a copy of the current SourceMesh stored in MeshOut
*/
virtual void GetMeshCopy(FDynamicMesh3& MeshOut);
/**
* Get a reference to the current SourceMesh
*/
virtual const FDynamicMesh3& GetMeshRef() const;
/**
* This delegate is broadcast whenever the internal SourceMesh is updated
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FOnMeshModified, ADynamicMeshBaseActor*);
FOnMeshModified OnMeshModified;
protected:
/** The SourceMesh used to initialize the mesh Components in the various subclasses */
FDynamicMesh3 SourceMesh;
/** Accumulated time since Actor was created, this is used for the animated primitives when bRegenerateOnTick = true*/
double AccumulatedTime = 0;
/** Called whenever the initial Source mesh needs to be regenerated / re-imported. Calls EditMesh() to do so. */
virtual void OnMeshGenerationSettingsModified();
/** Called to generate or import a new source mesh. Override this to provide your own generated mesh. */
virtual void RegenerateSourceMesh(FDynamicMesh3& MeshOut);
/** Call this on a Mesh to compute normals according to the NormalsMode setting */
virtual void RecomputeNormals(FDynamicMesh3& MeshOut);
//
// Support for AABBTree / Spatial Queries
//
public:
UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
bool bEnableSpatialQueries = false;
UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
bool bEnableInsideQueries = false;
protected:
// This AABBTree is updated each time SourceMesh is modified if bEnableSpatialQueries=true or bEnableInsideQueries=true
FDynamicMeshAABBTree3 MeshAABBTree;
// This FastWindingTree is updated each time SourceMesh is modified if bEnableInsideQueries=true
TUniquePtr<TFastWindingTree<FDynamicMesh3>> FastWinding;
//
// Support for Runtime-Generated Collision
//
public:
UPROPERTY(EditAnywhere, Category = RuntimeCollisionOptions)
EDynamicMeshActorCollisionMode CollisionMode = EDynamicMeshActorCollisionMode::NoCollision;
//
// ADynamicMeshBaseActor API that subclasses must implement.
//
protected:
/**
* Called when the SourceMesh has been modified. Subclasses override this function to
* update their respective Component with the new SourceMesh.
*/
virtual void OnMeshEditedInternal();
//
// Standard UE4 Actor Callbacks. If you need to override these functions,
// make sure to call (eg) Super::Tick() or you will break the mesh updating functionality.
//
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostLoad() override;
virtual void PostActorCreated() override;
#if WITH_EDITOR
// called when property is modified. This will call OnMeshGenerationSettingsModified() to update the mesh
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
//
// Blueprint API
//
public:
/**
* Update SourceMesh by reading external mesh file at Path. Optionally flip orientation and recompute normals.
* Note: Path may be relative to Content folder, otherwise it must be an absolute path.
* @return false if mesh read failed
*/
UFUNCTION(BlueprintCallable)
bool ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals);
/** Copy the SourceMesh of OtherMesh into our SourceMesh, and optionally recompute normals */
UFUNCTION(BlueprintCallable)
void CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals);
//
// Mesh Spatial Queries API
//
public:
/**
* Find NearestMeshWorldPoint on SourceMesh to WorldPoint, as well as NearestTriangle ID and barycentric coordinates of NearestMeshWorldPoint in triangle
* @return distance to point
*/
UFUNCTION(BlueprintCallable)
float DistanceToPoint(FVector WorldPoint, FVector& NearestMeshWorldPoint, int& NearestTriangle, FVector& TriBaryCoords);
/**
* @return nearest world-space point on SourceMesh to WorldPoint
*/
UFUNCTION(BlueprintCallable)
FVector NearestPoint(FVector WorldPoint);
/**
* @return true if mesh contains WorldPoint, which is defined as the mesh winding number being >= WindingThreshold
*/
UFUNCTION(BlueprintCallable)
bool ContainsPoint(FVector WorldPoint, float WindingThreshold = 0.5);
/**
* Calculate intersection of given 3D World-Space ray defined by (RayOrigin,RayDirection) with the SourceMesh.
* If hit, returns WorldHitPoint position, distance along ray in HitDistance, NearestTriangle ID, and barycentric coordinates of hit point in triangle
* Pass MaxDistance > 0 to limit the allowable ray-hit distance
* @return true if hit is found
*/
UFUNCTION(BlueprintCallable)
bool IntersectRay(FVector RayOrigin, FVector RayDirection, FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords, float MaxDistance = 0);
//
// Mesh Modification API
//
public:
/** Compute the specified a Boolean operation with OtherMesh (transformed to world space) and store in our SourceMesh */
UFUNCTION(BlueprintCallable)
void BooleanWithMesh(ADynamicMeshBaseActor* OtherMesh, EDynamicMeshActorBooleanOperation Operation);
/** Subtract OtherMesh from our SourceMesh */
UFUNCTION(BlueprintCallable)
void SubtractMesh(ADynamicMeshBaseActor* OtherMesh);
/** Union OtherMesh with our SourceMesh */
UFUNCTION(BlueprintCallable)
void UnionWithMesh(ADynamicMeshBaseActor* OtherMesh);
/** Intersect OtherMesh with our SourceMesh */
UFUNCTION(BlueprintCallable)
void IntersectWithMesh(ADynamicMeshBaseActor* OtherMesh);
/** Create a "solid" verison of SourceMesh by voxelizing with the fast winding number at the given grid resolution */
UFUNCTION(BlueprintCallable)
void SolidifyMesh(int VoxelResolution = 64, float WindingThreshold = 0.5);
/** Simplify current SourceMesh to the target triangle count */
UFUNCTION(BlueprintCallable)
void SimplifyMeshToTriCount(int32 TargetTriangleCount);
public:
/** @return number of triangles in current SourceMesh */
UFUNCTION(BlueprintCallable)
int GetTriangleCount();
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "CoreMinimal.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Read mesh in OBJ format from the given path into a FDynamicMesh3.
* @param bNormals should normals be imported into primary normal attribute overlay
* @param bTexCoords should texture coordinates be imported into primary UV attribute overlay
* @param bVertexColors should normals be imported into per-vertex colors
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for importing to UE4 from other apps.
* @param return false if read failed
*/
RUNTIMEGEOMETRYUTILS_API bool ReadOBJMesh(
const FString& Path,
FDynamicMesh3& MeshOut,
bool bNormals,
bool bTexCoords,
bool bVertexColors,
bool bReverseOrientation);
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "CoreMinimal.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Write mesh to the given output path in OBJ format.
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
* @param return false if write failed
*/
RUNTIMEGEOMETRYUTILS_API bool WriteOBJMesh(
const FString& OutputPath,
const FDynamicMesh3& Mesh,
bool bReverseOrientation);
/**
* Write set of meshes to the given output path in OBJ format.
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
* @param return false if write failed
*/
RUNTIMEGEOMETRYUTILS_API bool WriteOBJMeshes(
const FString& OutputPath,
const TArray<FDynamicMesh3>& Meshes,
bool bReverseOrientation);
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicPMCActor.generated.h"
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicPMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicPMCActor();
public:
UPROPERTY(VisibleAnywhere)
UProceduralMeshComponent* MeshComponent = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdatePMCMesh();
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SimpleDynamicMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicSDMCActor.generated.h"
class FDynamicMesh3;
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicSDMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicSDMCActor();
public:
UPROPERTY(VisibleAnywhere)
USimpleDynamicMeshComponent* MeshComponent = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdateSDMCMesh();
};

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/StaticMesh.h"
#include "Components/StaticMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicSMCActor.generated.h"
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicSMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicSMCActor();
public:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComponent = nullptr;
UPROPERTY(Transient)
UStaticMesh* StaticMesh = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostLoad() override;
virtual void PostActorCreated() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdateSMCMesh();
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/StaticMesh.h"
#include "ProceduralMeshComponent.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Reinitialize the given StaticMesh with the input FDynamicMesh3.
* This calls StaticMesh->BuildFromMeshDescriptions(), which can be used at Runtime (vs StaticMesh->Build() which cannot)
*/
RUNTIMEGEOMETRYUTILS_API void UpdateStaticMeshFromDynamicMesh(
UStaticMesh* StaticMesh,
const FDynamicMesh3* Mesh);
/**
* Initialize a ProceduralMeshComponent with a single section defined by the given FDynamicMesh3.
* @param bUseFaceNormals if true, each triangle is shaded with per-triangle normal instead of split-vertex normals from FDynamicMesh3 overlay
* @param bInitializeUV0 if true, UV0 is initialized, otherwise it is not (set to 0)
* @param bInitializePerVertexColors if true, per-vertex colors on the FDynamicMesh3 are used to initialize vertex colors of the PMC
*/
RUNTIMEGEOMETRYUTILS_API void UpdatePMCFromDynamicMesh_SplitTriangles(
UProceduralMeshComponent* Component,
const FDynamicMesh3* Mesh,
bool bUseFaceNormals,
bool bInitializeUV0,
bool bInitializePerVertexColors,
bool bCreateCollision);
}

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FRuntimeGeometryUtilsModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,61 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class RuntimeGeometryUtils : ModuleRules
{
public RuntimeGeometryUtils(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"GeometricObjects",
"DynamicMesh",
"ProceduralMeshComponent",
"ModelingComponents",
"RenderCore"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"MeshDescription",
"StaticMeshDescription",
"GeometryAlgorithms",
"MeshConversion",
"MeshUtilities2"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}