Not going to lie, I’m a complete novice when it comes to C++ programming. I have been trying to get the vertices of a static mesh triangle in order to calculate an interpolated normal to make movement over low-poly meshes smooth at any angle. I was directed to a function on a blog by someone in order to achieve this, and the actual math to determine the barycentric coordinates at the bottom works perfectly (I tested it in Blueprint) but there are missing functions in the first part that actually grabs the vertex information from the static mesh/spline mesh in the beginning part. I imagine it is outdated references to functions that names/locations have been changed. I’ve been trying to fix it on my own and had absolutely no success. If anyone could help that would be amazing! Also any hints for which header files I need to include with the updated code would be nice as well.
Here’s a link to the blog post: UE4 Smoothed Normal from Line Trace in Packaged Builds - wacki
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* = PositionVertexBuffer->VertexPosition(index);
VertexNormals* = 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*, 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*, SplineMeshComp->ForwardAxis) = 0;
// Transform position and normal into spline mesh space
VertexPositions* = SliceTransform.TransformPosition(VertexPositions*);
VertexNormals* = SliceTransform.TransformVector(VertexNormals*);
}
// Transform position and normal into world space
VertexPositions* = ComponentTransform.TransformPosition(VertexPositions*);
VertexNormals* = ComponentTransform.TransformVector(VertexNormals*);
}
// 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*, 16, DebugColor, false, DebugDrawDuration);
// draw triangle edges
::DrawDebugLine(StaticMeshComp->GetWorld(), VertexPositions*, VertexPositions(i + 1) % 3], DebugColor, false, DebugDrawDuration);
// triangle normals
::DrawDebugLine(StaticMeshComp->GetWorld(), VertexPositions*, VertexPositions* + VertexNormals* * 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;
}