可用
This commit is contained in:
BIN
Plugins/RuntimeGeometryUtils/Content/BlueMaterial.uasset
Normal file
BIN
Plugins/RuntimeGeometryUtils/Content/BlueMaterial.uasset
Normal file
Binary file not shown.
BIN
Plugins/RuntimeGeometryUtils/Content/RedMaterial.uasset
Normal file
BIN
Plugins/RuntimeGeometryUtils/Content/RedMaterial.uasset
Normal file
Binary file not shown.
BIN
Plugins/RuntimeGeometryUtils/Resources/Icon128.png
Normal file
BIN
Plugins/RuntimeGeometryUtils/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
42
Plugins/RuntimeGeometryUtils/RuntimeGeometryUtils.uplugin
Normal file
42
Plugins/RuntimeGeometryUtils/RuntimeGeometryUtils.uplugin
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@@ -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);
|
||||
});
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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)
|
@@ -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.
|
@@ -0,0 +1,334 @@
|
||||
# tinyobjloader
|
||||
|
||||
[](https://travis-ci.org/tinyobjloader/tinyobjloader)
|
||||
|
||||
[](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master)
|
||||
|
||||
[](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master)
|
||||
|
||||
[](https://coveralls.io/github/syoyo/tinyobjloader?branch=master)
|
||||
|
||||
[](https://aur.archlinux.org/packages/tinyobjloader)
|
||||
|
||||
[](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
|
||||
|
||||

|
||||
|
||||
tinyobjloader can successfully load 6M triangles Rungholt scene.
|
||||
http://casual-effects.com/data/index.html
|
||||
|
||||

|
||||
|
||||
* [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.
|
@@ -0,0 +1,2 @@
|
||||
#define TINYOBJLOADER_IMPLEMENTATION
|
||||
#include "tiny_obj_loader.h"
|
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
};
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
};
|
@@ -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();
|
||||
};
|
@@ -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();
|
||||
};
|
@@ -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);
|
||||
|
||||
}
|
@@ -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;
|
||||
};
|
@@ -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 ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user