1、通过MeshDescription在运行时构建StaticMesh并设置碰撞、修改材质

1、MyActor.h

c++ 复制代码
//MyActor.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class CONTROLLER_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyActor();
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Polygon")
	TObjectPtr<class UStaticMeshComponent> PolygonComponent;
	FPolygonGroupID GroupId;
	UPROPERTY(EditAnywhere, Category = "Polygon")
	TArray<FVector> Coodinates;
	TObjectPtr<class UStaticMesh> Polygon;
	UPROPERTY(EditAnywhere, Category = "Polygon")
	UMaterialInterface* Material;
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void OnConstruction(const FTransform& Transform) override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	TObjectPtr<class UStaticMesh> BuildPolygon(TArray<FVector> InPositions);

};

2、MyActor.cpp

c++ 复制代码
//MyActor.cpp


#include "MyActor.h"

#include "StaticMeshAttributes.h"

#include "CompGeom/PolygonTriangulation.h"
#include <StaticMeshOperations.h>
#include <MeshDescriptionBuilder.h>

// Sets default values
AMyActor::AMyActor()
{
 	// 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;
	PolygonComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PolygonComponent"), false);
	SetRootComponent(PolygonComponent);
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	if(Coodinates.Num())
	{
		Material = LoadObject<UMaterial>(this, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
		PolygonComponent->SetStaticMesh(BuildPolygon(Coodinates));
		PolygonComponent->SetMaterial(0,Material);
		//PolygonComponent->SetForcedLodModel(1);

	}
}

void AMyActor::OnConstruction(const FTransform& Transform)
{
	if (!IsTemplate()) {
		if (Coodinates.Num())
		{
			//加载虚幻引擎自带的材质
			Material = LoadObject<UMaterial>(this, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
			//将生成的StaticMesh挂载到StaticMeshComponent上
			BuildPolygon(Coodinates);
			PolygonComponent->SetStaticMesh(Polygon);
			//在StaticMeshComponent上设置材质,坑1:StaticMesh也有一个SetMaterial方法,但是它是仅在编辑器状态下可用的,调用了这个方法打包时会报错,打包就失败了,而StaticMeshComponent的SetMaterial方法没有这个问题,且材质能够成功应用到StaticMesh上
			PolygonComponent->SetMaterial(0, Material);
		}
	}
}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
FBox2D computeMinRectangle(TArray<FVector2D> InPositions)
{
	FBox2D result;
	uint8 num = InPositions.Num();
	if (num < 2)
	{
		return result;
	}
	const double doubleMin = std::numeric_limits<double>::min();
	const double doubleMax = std::numeric_limits<double>::max();

	double minX = doubleMax, minY = doubleMax, maxX = doubleMin, maxY = doubleMin;
	for (uint8 i = 0; i < num; ++i)
	{
		FVector2D currentPoint = InPositions[i];

		maxX = maxX > currentPoint.X ? maxX : currentPoint.X;
		maxY = maxY > currentPoint.Y ? maxY : currentPoint.Y;

		minX = minX < currentPoint.X ? minX : currentPoint.X;
		minY = minY < currentPoint.Y ? minY : currentPoint.Y;
	}
	result.Max = FVector2D(maxX, maxY);
	result.Min = FVector2D(minX, minY);

	return result;
}
TObjectPtr<class UStaticMesh> AMyActor::BuildPolygon(TArray<FVector> InPositions)
{
	if (InPositions.Num()) {
		TSet<FVector> UniquePoints;

		for (int32 i = 0; i < InPositions.Num(); ++i)
		{
			if (!UniquePoints.Contains(InPositions[i]))
			{
				UniquePoints.Add(InPositions[i]);
			}
		}
		InPositions.Empty();
		InPositions = UniquePoints.Array();
	}
	if (InPositions.Num() < 3) {
		UE_LOG(LogTemp, Error, TEXT("Error:After removing duplicate points,the length of the coordinates array must be greater than or equal to 3!!"))
			return nullptr;
	}
	FVector4f Color(1.0, 1.0, 1.0, 1.0);
	Polygon = NewObject<UStaticMesh>(this, FName("Polygon"));
	//坑2:添加材质插槽,StaticMesh有个名为AddMaterial的方法,看名字像是添加材质插槽,但是实际使用上,打包后不生效,而采用"GetStaticMaterials().Add(FStaticMaterial())"这种方式可以
	Polygon->GetStaticMaterials().Add(FStaticMaterial());

	FMeshDescription meshDesc;
	FStaticMeshAttributes attributes(meshDesc);
	//注册默认的静态模型属性
	attributes.Register();

	//FMeshDescriptionBuilder用于设置顶点、法线、UV坐标等信息
	FMeshDescriptionBuilder meshDescBuilder;
	meshDescBuilder.SetMeshDescription(&meshDesc);
	//允许使用多边形组
	meshDescBuilder.EnablePolyGroups();
	//设置UV坐标的级数为1
	meshDescBuilder.SetNumUVLayers(1);

	TArray<FVertexID> vertexIDs;
	const int count = InPositions.Num();
	vertexIDs.SetNum(count);
	TArray<FVector2D> Positions2D;
	for (int32 i = 0; i < count; ++i) {
		FVector& position = InPositions[i];
		vertexIDs[i] = meshDescBuilder.AppendVertex(position);
		Positions2D.Add(FVector2D(position.X, position.Y));
	}
	//创建一个多边形组,每个多边形组可以单独赋予不同的材质
	GroupId = meshDescBuilder.AppendPolygonGroup();


	TArray<UE::Geometry::FIndex3i> polyTriangleIndices;
	TArray<int32> PolyTriangleIndices;
	bool bOrientAsHoleFill = true;
	//耳切法生成多边形三角网格的顶点索引
	PolygonTriangulation::TriangulateSimplePolygon(InPositions, polyTriangleIndices, bOrientAsHoleFill);
	for (UE::Geometry::FIndex3i& indice : polyTriangleIndices) {
		PolyTriangleIndices.Add(indice.A);
		PolyTriangleIndices.Add(indice.B);
		PolyTriangleIndices.Add(indice.C);

	}
	//用于计算纹理坐标
	const FBox2D box = computeMinRectangle(Positions2D);
	auto ComputeUV = [&box](FVector InPosition) {
		FVector2D coord;
		coord.X = (InPosition.X - box.Min.X) / 100.0;
		coord.Y = (InPosition.Y - box.Min.Y) / 100.0;
		return coord;
		};
		
	for (int32 i = 0; i < polyTriangleIndices.Num(); ++i) {
		//将顶点添加到meshDescBuild中并获得顶点索引
		FVertexInstanceID index1 = meshDescBuilder.AppendInstance(vertexIDs[PolyTriangleIndices[i * 3]]);
		//设置指定索引的顶点的uv坐标
		meshDescBuilder.SetInstanceUV(index1, ComputeUV(meshDescBuilder.GetPosition(vertexIDs[PolyTriangleIndices[i * 3]])), 0);
		//设置指定索引的顶点的颜色
		meshDescBuilder.SetInstanceColor(index1, Color);

		FVertexInstanceID index2 = meshDescBuilder.AppendInstance(vertexIDs[PolyTriangleIndices[i * 3 + 1]]);
		meshDescBuilder.SetInstanceUV(index2, ComputeUV(meshDescBuilder.GetPosition(vertexIDs[PolyTriangleIndices[i * 3 + 1]])), 0);
		meshDescBuilder.SetInstanceColor(index2, Color);

		FVertexInstanceID index3 = meshDescBuilder.AppendInstance(vertexIDs[PolyTriangleIndices[i * 3 + 2]]);
		meshDescBuilder.SetInstanceUV(index3, ComputeUV(meshDescBuilder.GetPosition(vertexIDs[PolyTriangleIndices[i * 3 + 2]])), 0);
		meshDescBuilder.SetInstanceColor(index3, Color);
		//设置三角形网格的顶点索引,并指定该网格属于哪个多边形组
		meshDescBuilder.AppendTriangle(index1, index2, index3, GroupId);

	}

	UStaticMesh::FBuildMeshDescriptionsParams mdParams;
	mdParams.bBuildSimpleCollision = true;//构建简单碰撞
	mdParams.bFastBuild = true;//快速构建
	
	//计算三角形网格的切线和法线
	FStaticMeshOperations::ComputeTriangleTangentsAndNormals(meshDesc);
	
	TArray<const FMeshDescription*> meshDescPtrs;
	meshDescPtrs.Emplace(&meshDesc);
	//构建StaticMesh(构建Mesh的方法不止这一种,其中有种方法要调用InitResources,然后还要设置Lod,但是通过BuildFromMeshDescriptions方法不需要这么做,其方法内部已经封装好了)
	Polygon->BuildFromMeshDescriptions(meshDescPtrs, mdParams);
	
	//坑3:如果要给StaticMeshComponent绑定鼠标的click、hover等时间的话,需要调用下面这段代码以启用复杂碰撞,因为click等事件也是基于射线检测进行判断的,但是它检测的是复杂碰撞(TraceComplex参数值为true),因此如果不调用下面这段代码,click等事件是不会生效的。注意:这里不要创建BodySetup,在前面的BuildFromMeshDescriptions方法的函数实现里面已经为StaticMesh创建了一个BodySetup,并为其赋予了一些值。
		if (UBodySetup* bodySetup = Polygon->GetBodySetup())
	{
		bodySetup->DefaultInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
		bodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
		bodySetup->bGenerateMirroredCollision = false;
	}
	return Polygon;
}

3、总结

1)运行时修改StaticMesh的材质要调用StaticMeshComponent的SetMaterial方法而不是StaticMesh的SetMaterial方法,因为StaticMesh的SetMaterial方法仅在编辑器状态下可用,会导致你打包失败;

2)想要在运行时修改StaticMesh的材质,首先要确保StaticMesh有材质插槽,为StaticMesh增加材质插槽的方法如上,一定不能使用StaticMesh的AddMaterial方法,运行时状态下无效;

3)想要给StaticMesh绑定click、hover等事件,需要为其启用复杂碰撞,代码如上,但是不使用那段代码也有碰撞,只是click、hover等事件不生效。

相关推荐
车载诊断技术5 分钟前
电子电气架构 --- 什么是EPS?
网络·人工智能·安全·架构·汽车·需求分析
若亦_Royi8 分钟前
C++ 的大括号的用法合集
开发语言·c++
KevinRay_10 分钟前
Python超能力:高级技巧让你的代码飞起来
网络·人工智能·python·lambda表达式·列表推导式·python高级技巧
2301_819287121 小时前
ce第六次作业
linux·运维·服务器·网络
CIb0la1 小时前
GitLab 停止为中国区用户提供 GitLab.com 账号服务
运维·网络·程序人生
Black_mario1 小时前
链原生 Web3 AI 网络 Chainbase 推出 AVS 主网, 拓展 EigenLayer AVS 应用场景
网络·人工智能·web3
中科岩创3 小时前
中科岩创边坡自动化监测解决方案
大数据·网络·物联网
九流下半3 小时前
threejs 建筑设计(室内设计)软件 技术调研之四 墙体添加真实门窗并保持原材质
材质·threejs 画墙体·threejs 墙体添加门窗
ue星空3 小时前
UE5材质系统之PBR材质
ue5·材质
哈哈地图3 小时前
Cesium材质——Material
材质·cesium