UE4 Smoothed Normal from Line Trace in Packaged Builds
gamedev programming ue4
I needed a way to get a smoothed surface normal from a line trace in Unreal Engine 4. So of course the first thing I did was research the topic. I found a lot of good resources that I linked at the bottom of this post. However none of the forum posts mentioned the solution to accessing mesh data in a packaged build. This might’ve been because I was searching the wrong keywords. I included my code to calculate the interpolated surface normal for both Static Meshes and Spline Meshes in Unreal Engine 4.
Most of the resources I found were talking about getting UV coordinates for a Line Trace hit. And many didn’t consider Spline Meshes at all which is my primary usecase. And one important thing I didn’t find mentioned anywhere is that you need to enable Allow CPUAccess on static meshes that you want to access during runtime in a packaged build. I found that one while digging through the source code, although it’s a pretty obvious thing in hindsight.
To be able to access mesh data in a packaged build bAllowCPUAccess must to be set to true.
The following code is part of my static c++ function library. It accepts a FHitResult reference and calculates an interpolated normal based on the triangle that was hit. This works for both Static Mesh as well as Spline Mesh Components. However this code is by no means perfect and I am sure there is a lot that could be improved.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
bool GetSmoothNormalFromTrace(const FHitResult& HitResult, FVector& Normal, bool DebugDraw)
{
// We don't care if this isn't a blocking hit
if(!HitResult.bBlockingHit)
return false;
// See if a static mesh and/or a spline mesh component were hit
UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(HitResult.GetComponent());
USplineMeshComponent* SplineMeshComp = Cast<USplineMeshComponent>(HitResult.GetComponent());
// If we hit a spline mesh component we should also have hit a static mesh component
if(!StaticMeshComp) return false;
// Retrieve static mesh. If we hit a spline mesh component we also hit a static mesh component at the same time.
// So we can just use it to get to the actual static mesh.
UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh();
// Return if the static mesh isn't set (shouldn't happen)
if(!StaticMesh)
return false;
// In cooked builds we need to have this flag set or we'll crash when trying to access mesh data
if(!StaticMesh->bAllowCPUAccess)
return false;
// Return if RenderData is invalid
if(!StaticMesh->RenderData)
return false;
// No valid mesh data on lod 0 (shouldn't happen)
if(!StaticMesh->RenderData->LODResources.IsValidIndex(0))
return false;
int FaceIndex = HitResult.FaceIndex;
FTransform ComponentTransform = StaticMeshComp->GetComponentTransform();
FStaticMeshVertexBuffer* VertexBuffer = &StaticMesh->RenderData->LODResources[0].VertexBuffer;
FPositionVertexBuffer* PositionVertexBuffer = &StaticMesh->RenderData->LODResources[0].PositionVertexBuffer;
FIndexArrayView IndexBuffer = StaticMesh->RenderData->LODResources[0].IndexBuffer.GetArrayView();
// Storage for the actual triangle verteces
FVector VertexPositions[3];
FVector VertexNormals[3];
for(int i = 0; i < 3; i++) {
// Get vertex index
uint32 index = IndexBuffer[FaceIndex * 3 + i];
// Get vertex position and normal
VertexPositions[i] = PositionVertexBuffer->VertexPosition(index);
VertexNormals[i] = VertexBuffer->VertexTangentZ(index);
// Transform position and normal into spline mesh space
if(SplineMeshComp) {
// Get transform along spline component
const FTransform SliceTransform = SplineMeshComp->CalcSliceTransform(USplineMeshComponent::GetAxisValue(VertexPositions[i], SplineMeshComp->ForwardAxis));
// Remove spline forward axis from vertex position, it will be added back by transforming the position into spline mesh space
USplineMeshComponent::GetAxisValue(VertexPositions[i], SplineMeshComp->ForwardAxis) = 0;
// Transform position and normal into spline mesh space
VertexPositions[i] = SliceTransform.TransformPosition(VertexPositions[i]);
VertexNormals[i] = SliceTransform.TransformVector(VertexNormals[i]);
}
// Transform position and normal into world space
VertexPositions[i] = ComponentTransform.TransformPosition(VertexPositions[i]);
VertexNormals[i] = ComponentTransform.TransformVector(VertexNormals[i]);
}
// Determine the barycentric coordinates
FVector U = VertexPositions[1] - VertexPositions[0];
FVector V = VertexPositions[2] - VertexPositions[0];
FVector W = HitResult.ImpactPoint - VertexPositions[0];
FVector vCrossW = FVector::CrossProduct(V, W);
FVector vCrossU = FVector::CrossProduct(V, U);
if(FVector::DotProduct(vCrossW, vCrossU) < 0.0f) {
return false;
}
FVector uCrossW = FVector::CrossProduct(U, W);
FVector uCrossV = FVector::CrossProduct(U, V);
if(FVector::DotProduct(uCrossW, uCrossV) < 0.0f) {
return false;
}
float Denom = uCrossV.Size();
float b1 = vCrossW.Size() / Denom;
float b2 = uCrossW.Size() / Denom;
float b0 = 1.0f - b1 - b2;
// Determine the hit normal
Normal.X = b0 * VertexNormals[0].X + b1 * VertexNormals[1].X + b2 * VertexNormals[2].X;
Normal.Y = b0 * VertexNormals[0].Y + b1 * VertexNormals[1].Y + b2 * VertexNormals[2].Y;
Normal.Z = b0 * VertexNormals[0].Z + b1 * VertexNormals[1].Z + b2 * VertexNormals[2].Z;
// Just to be safe here
Normal.Normalize();
// Debug draw
#if !UE_BUILD_SHIPPING
if(DebugDraw) {
// Quick and dirty debug draw
FColor DebugColor = FColor::Red;
float DebugDrawDuration = 30.0f;
for(int i = 0; i < 3; i++) {
// draw triangle points
::DrawDebugPoint(StaticMeshComp->GetWorld(), VertexPositions[i], 16, DebugColor, false, DebugDrawDuration);
// draw triangle edges
::DrawDebugLine(StaticMeshComp->GetWorld(), VertexPositions[i], VertexPositions[(i+1)%3], DebugColor, false, DebugDrawDuration);
// triangle normals
::DrawDebugLine(StaticMeshComp->GetWorld(), VertexPositions[i], VertexPositions[i] + VertexNormals[i] * 200.0f, DebugColor, false, DebugDrawDuration);
}
// draw actual impact normal
::DrawDebugPoint(StaticMeshComp->GetWorld(), HitResult.ImpactPoint, 16, DebugColor, false, DebugDrawDuration);
::DrawDebugLine(StaticMeshComp->GetWorld(), HitResult.ImpactPoint, HitResult.ImpactPoint + HitResult.ImpactNormal * 200.0f, DebugColor, false, DebugDrawDuration);
::DrawDebugLine(StaticMeshComp->GetWorld(), HitResult.ImpactPoint, HitResult.ImpactPoint + Normal * 200.0f, DebugColor, false, DebugDrawDuration);
}
#endif //!UE_BUILD_SHIPPING
return true;
}
Resources used:
- UE4 Forum - Question about traces and geometry.
- UE4 Forum - Accessing Vertex Positions of static mesh
- UE4 Forum - How to get vertex positions of spline mesh component in world space?
- UE4 Forum - Accessing Vertex Positions of static mesh
- UE4 AnswerHub - How can I get polygon normals of a static mesh?
- UE4 AnswerHub - How to get triangles of a Static Mesh?
- UE4 AnswerHub - How to use Procedural Mesh Component in Blueprint?h
- UE4 Source Code - Transforming Positions Into SplineMesh Space
- UE4 Source Code - GetSectionFromStaticMesh
- UE4 Wiki - Accessing mesh triangles and vertex positions in build (Although this one is a bit older)