Unreal Engine 4 – Cookbook

Dear UE4 developers,

on this page I’d like to publish all code snippets and useful hint I found or created to make life easier developing games or engine plugins.

  1. Line to line intersection
  2. Line to rectangle intersection
  3. Finding all materials in a given asset folder
  4. Extrude a mesh
  5. Make an actor tick in the editor
  6. Calculate angle between two 3d vectors

 

1. Line to line intersection

Sometimes it is useful to calculate the intersection of lines. The function below supports intersections in 2D and 3D spaces and is cheap since it is based on a mathematical calculation instead of e.g a brute force method. Just enter the the start and end point of both lines to check. The fifth parameter will be filled with the intersection point if there is any. The function returns true if both lines intersect and false if they dont.
 

bool PCGUtils::lineToLineIntersection(const FVector& fromA, const FVector& fromB, const FVector& toA, const FVector& toB, FVector& _outIntersection)
{
	FVector da = fromB - fromA;
	FVector db = toB - toA;
	FVector dc = toA - fromA;
 
	if (FVector::DotProduct(dc, FVector::CrossProduct(da, db)) != 0.0) {
		return false;
	}
 
	FVector crossDaDb = FVector::CrossProduct(da, db);
	float prod = crossDaDb.X * crossDaDb.X + crossDaDb.Y * crossDaDb.Y + crossDaDb.Z * crossDaDb.Z;
 
	float res = FVector::DotProduct(FVector::CrossProduct(dc, db), FVector::CrossProduct(da, db) / prod);
	if (res >= 0.0f && res <= 1.0f) {
		_outIntersection = fromA + da * FVector(res, res, res);
		return true;
	}
 
	return false;
}

2. Line to rectangle intersection

Based on the Line to line intersection algorithm this function serves to calculate the intersection of a line with a rectangle. It is only meant to be used with a two dimensional rectangle otherwise you should use Unreal’s FMath::LineBoxIntersection. This function checks line by line of the corresponding line intersects with one of the four lines of our rectangle. It returns the intersection point as well as the line id the input line intersects with (starting on the bottom left counter clockwise).
 

bool PCGUtils::lineToRectangleIntersection(const FVector& rectMin, const FVector& rectMax, const FVector& lineStart, const FVector& lineEnd, int outLineId, FVector& outIntersection)
{
	// First line of rectangle
	bool res = PCGUtils::lineToLineIntersection(FVector(rectMin.X, rectMin.Y, 0), lineStart, FVector(rectMax.X, rectMin.Y, 0), lineEnd, outIntersection);
	if (res) {
		outLineId = 0;
		return true;
	}
 
	// Second line of rectangle
	res = PCGUtils::lineToLineIntersection(FVector(rectMax.X, rectMin.Y, 0), lineStart, FVector(rectMax.X, rectMax.Y, 0), lineEnd, outIntersection);
	if (res) {
		outLineId = 1;
		return true;
	}
 
	// Third line of rectangle
	res = PCGUtils::lineToLineIntersection(FVector(rectMax.X, rectMax.Y, 0), lineStart, FVector(rectMin.X, rectMax.Y, 0), lineEnd, outIntersection);
	if (res) {
		outLineId = 2;
		return true;
	}
 
	// Fourth line of rectangle
	res = PCGUtils::lineToLineIntersection(FVector(rectMin.X, rectMax.Y, 0), lineStart, FVector(rectMin.X, rectMin.Y, 0), lineEnd, outIntersection);
	if (res) {
		outLineId = 3;
		return true;
	}
 
	// No intersection at all?
	outLineId = -1;
	return false;
}

3. Finding all materials in a given asset folder

This simple method lists all objects from the type MaterialInstance in an asset folder in the editor.
 

TArray<UMaterialInstance*>* PCGUtils::getMaterialInstancesFromPath(FString _path) {
 
	TArray<UMaterialInstance*>* result = new TArray<UMaterialInstance*>();
 
	UObjectLibrary *lib = UObjectLibrary::CreateLibrary(UMaterialInstance::StaticClass(), false, true);
	UE_LOG(LogTemp, Warning, TEXT("Searching for material instances ..."));
	lib->LoadAssetDataFromPath(_path);
	TArray<FAssetData> assetData;
	lib->GetAssetDataList(assetData);
	UE_LOG(LogTemp, Warning, TEXT("Found %d"), assetData.Num());
 
	for (FAssetData asset : assetData) {
		UMaterialInstance* mi = Cast<UMaterialInstance>(asset.GetAsset());
		if (mi) {
			UE_LOG(LogTemp, Warning, TEXT("Material instance %s"), *mi->GetName());
			result->Add(mi);
		}
	}
	return result;
}

4. Extrude a mesh

To extrude a mesh use this function. It awaits vertices and indices (so you do not actually pass a mesh object) and a direction in which all vertices should be extruded. The fourth parameter duplicateEdgeVertices can be used to duplicate all vertices at mesh edges so that you could create a better UV map.
 

void MeshUtils::extrude(TArray<FVector>* vertices, TArray<int32>* indices, FVector direction, bool duplicateEdgeVertices)
{
	if (((direction.X != 0) && (direction.Y != 0)) || ((direction.X != 0) && (direction.Z != 0)) ||
		((direction.Y != 0) && (direction.Z != 0))) {
		UE_LOG(LogTemp, Error, TEXT("Extrusion may only be applied in one direction."));
		return;
	}
 
	bool extrusionGrowing = direction.X + direction.Y + direction.Z > 0;
 
	int32 indexCount = indices->Num();
	int32 vertexCount = vertices->Num();
 
	// 1. Get boundary edges
	std::vector<p2t::Point*>* allEdges = new std::vector<p2t::Point*>();
	std::vector<p2t::Point*>* boundaryEdges = new std::vector<p2t::Point*>();
 
 
	for (int i = 0; i < indexCount - 2; i += 3) {
		allEdges->push_back(new p2t::Point((*indices)[i], (*indices)[i + 1]));
		allEdges->push_back(new p2t::Point((*indices)[i + 1], (*indices)[i + 2]));
		allEdges->push_back(new p2t::Point((*indices)[i + 2], (*indices)[i]));
	}
 
 
#ifdef UE_BUILD_DEBUG
	UE_LOG(LogTemp, Warning, TEXT("Found %d edges"), allEdges->size());
#endif	
 
	bool found = false;
	for (int i = allEdges->size() - 1; i >= 0; i--) {
 
		found = false;
		for (int j = allEdges->size() - 1; j >= 0; j--) {
 
			if ((allEdges->at(i)->x == allEdges->at(j)->y) && (allEdges->at(i)->y == allEdges->at(j)->x)) {
				found = true;
				break;
			}
 
		}
 
		if (found == false) {
			boundaryEdges->push_back(allEdges->at(i));
		}
	}
 
#ifdef UE_BUILD_DEBUG
	UE_LOG(LogTemp, Warning, TEXT("Found %d boundary edges"), boundaryEdges->size());
#endif
 
	// 2. Add all vertices again moved by 'direction'
	for (int i = 0; i < vertexCount; i++) {
		FVector newVert = (*vertices)[i] + direction;
		vertices->Add(newVert);
	}
 
 
	// 3. Create the corresponding indices
	UE_LOG(LogTemp, Warning, TEXT("Index count %d"), indexCount);
	if (extrusionGrowing) {
		for (int32 i = 0; i < indexCount; i++) {
			indices->Add(vertexCount + (*indices)[i]);
		}
	}
	else {
		for (int32 i = indexCount-1; i >= 0; i--) {
			indices->Add(vertexCount + (*indices)[i]);
		}
	}
 
	// 4. Add the side meshes
	if (extrusionGrowing) {
		for (int i = 0; i < boundaryEdges->size(); i++) {
 
			if (!duplicateEdgeVertices) {
				indices->Add(boundaryEdges->at(i)->x);
				indices->Add(boundaryEdges->at(i)->y);
				indices->Add(boundaryEdges->at(i)->y + vertexCount);
 
				indices->Add(boundaryEdges->at(i)->y + vertexCount);
				indices->Add(boundaryEdges->at(i)->x + vertexCount);
				indices->Add(boundaryEdges->at(i)->x);
			}
			else {
 
				int32 i1 = (int32)boundaryEdges->at(i)->x;
				int32 i2 = (int32)boundaryEdges->at(i)->y;
				int32 i3 = (int32)boundaryEdges->at(i)->x + vertexCount;
				int32 i4 = (int32)boundaryEdges->at(i)->y + vertexCount;
 
				UE_LOG(LogTemp, Warning, TEXT("New indices %d %d %d %d"), i1, i2, i3, i4);
 
				FVector v1 = (*vertices)[i1];
				FVector v2 = (*vertices)[i2];
				FVector v3 = (*vertices)[i3];
				FVector v4 = (*vertices)[i4];
 
				int32 newVertex1 = vertices->Add(v1);
				int32 newVertex2 = vertices->Add(v2);
				int32 newVertex3 = vertices->Add(v3);
				int32 newVertex4 = vertices->Add(v4);
 
				indices->Add(newVertex1);
				indices->Add(newVertex2);
				indices->Add(newVertex4);
 
				indices->Add(newVertex4);
				indices->Add(newVertex3);
				indices->Add(newVertex1);
			}
		}
	}
	else {
		for (int i = 0; i < boundaryEdges->size(); i++) {
 
			if (!duplicateEdgeVertices) {
				indices->Add(boundaryEdges->at(i)->y + vertexCount);
				indices->Add(boundaryEdges->at(i)->y);
				indices->Add(boundaryEdges->at(i)->x);
 
				indices->Add(boundaryEdges->at(i)->x);
				indices->Add(boundaryEdges->at(i)->x + vertexCount);
				indices->Add(boundaryEdges->at(i)->y + vertexCount);
			}
			else {
 
				int32 i1 = (int32)boundaryEdges->at(i)->x;
				int32 i2 = (int32)boundaryEdges->at(i)->y;
				int32 i3 = (int32)boundaryEdges->at(i)->x + vertexCount;
				int32 i4 = (int32)boundaryEdges->at(i)->y + vertexCount;
 
				UE_LOG(LogTemp, Warning, TEXT("New indices %d %d %d %d"), i1, i2, i3, i4);
 
				FVector v1 = (*vertices)[i1];
				FVector v2 = (*vertices)[i2];
				FVector v3 = (*vertices)[i3];
				FVector v4 = (*vertices)[i4];
 
				UE_LOG(LogTemp, Warning, TEXT("New vertices (%f %f %f), (%f %f %f), (%f %f %f), (%f %f %f)"), v1.X, v1.Y, v1.Z, v2.X, v2.Y, v2.Z, v3.X, v3.Y, v3.Z, v4.X, v4.Y, v4.Z);
 
				int32 newVertex1 = vertices->Add(v1);
				int32 newVertex2 = vertices->Add(v2);
				int32 newVertex3 = vertices->Add(v3);
				int32 newVertex4 = vertices->Add(v4);
 
				indices->Add(newVertex4);
				indices->Add(newVertex2);
				indices->Add(newVertex1);
 
				indices->Add(newVertex1);
				indices->Add(newVertex3);
				indices->Add(newVertex4);
			}
		}
	}
}

5. Make an actor tick in the editor

Sometimes you want to make your actor tick even if your game is not running, e.g. to draw debug lines or points in the view. The following method shows how to do so.
First add a the following line to the header file that is based on AActor as base class.
 

virtual bool ShouldTickIfViewportsOnly() const override;

 
The implement the message in the source file.

bool AMyActor::ShouldTickIfViewportsOnly() const
{
	return true;
}

6. Calculate angle between two 3d vectors

Static function to calculate the angle between two 3d vectors.

float MyClass::angleBetweenVectors(const FVector& vec1, const FVector& vec2) {
	return FMath::Atan2(FVector::CrossProduct(vec1, vec2).Normalize(), FVector::DotProduct(vec1, vec2));
}