That’s a good test since triangles are straightforward, it’s definitely wrong somehow. Doing the math in BP is less than ideal for me.
Is your project setup for C++? If so, you could use this, it’s partially ripped from an old VR magic gesture plugin that was never finished, but I tested it and it mostly works with some low inaccuracy but the formula seems correct so give it a try. If you don’t have your C++ project setup, please back your project up before doing so, it’s very possible to have problems doing so. I’m not working from my rig with source installed so I couldn’t bundle it into a plugin.
Once you get it working with the project, go into the spline BP and use this node it generates.
Here’s the CPP file:
#include "SplineAreaLibrary.h"
#include "Math/Vector.h"
#include "Math/Vector2D.h"
#include "Math/UnrealMathUtility.h"
float USplineAreaLibrary::CalculateProjectedSplineArea2D(const USplineComponent* SplineComponent, EPlaneProjection ProjectionPlane, int32 NumSamples)
{
//Warnings if messing up
if (!SplineComponent)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Invalid SplineComponent provided."));
return 0.0f;
}
if (NumSamples < 3)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Need at least 3 samples for area calculation. Provided: %d"), NumSamples);
return 0.0f;
}
const float SplineLength = SplineComponent->GetSplineLength();
if (SplineLength <= 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Spline length is zero or negative."));
return 0.0f;
}
TArray<FVector2D> ProjectedPoints;
ProjectedPoints.Reserve(NumSamples);
for (int32 i = 0; i < NumSamples; ++i)
{
const float Distance = (SplineLength / static_cast<float>(NumSamples)) * static_cast<float>(i);
const FVector WorldLocation = SplineComponent->GetWorldLocationAtDistanceAlongSpline(Distance);
FVector2D Point2D;
switch (ProjectionPlane)
{
case EPlaneProjection::XZ:
Point2D = FVector2D(WorldLocation.X, WorldLocation.Z);
break;
case EPlaneProjection::YZ:
Point2D = FVector2D(WorldLocation.Y, WorldLocation.Z);
break;
case EPlaneProjection::XY:
default:
Point2D = FVector2D(WorldLocation.X, WorldLocation.Y);
break;
}
ProjectedPoints.Add(Point2D);
}
// Shoelace forumula. Inaccurate or just floating point problems?
float ShoelaceSum = 0.0f;
const int32 NumPoints = ProjectedPoints.Num();
for (int32 i = 0; i < NumPoints; ++i)
{
const FVector2D& CurrentPoint = ProjectedPoints[i];
const FVector2D& NextPoint = ProjectedPoints[(i + 1) % NumPoints];
ShoelaceSum += (CurrentPoint.X * NextPoint.Y) - (CurrentPoint.Y * NextPoint.X);
}
const float Area = 0.5f * FMath::Abs(ShoelaceSum);
return Area;
}
and here’s the header:
#include "SplineAreaLibrary.h"
#include "Math/Vector.h"
#include "Math/Vector2D.h"
#include "Math/UnrealMathUtility.h"
float USplineAreaLibrary::CalculateProjectedSplineArea2D(const USplineComponent* SplineComponent, EPlaneProjection ProjectionPlane, int32 NumSamples)
{
//Warnings if messing up
if (!SplineComponent)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Invalid SplineComponent provided."));
return 0.0f;
}
if (NumSamples < 3)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Need at least 3 samples for area calculation. Provided: %d"), NumSamples);
return 0.0f;
}
const float SplineLength = SplineComponent->GetSplineLength();
if (SplineLength <= 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("CalculateProjectedSplineArea2D: Spline length is zero or negative."));
return 0.0f;
}
TArray<FVector2D> ProjectedPoints;
ProjectedPoints.Reserve(NumSamples);
for (int32 i = 0; i < NumSamples; ++i)
{
const float Distance = (SplineLength / static_cast<float>(NumSamples)) * static_cast<float>(i);
const FVector WorldLocation = SplineComponent->GetWorldLocationAtDistanceAlongSpline(Distance);
FVector2D Point2D;
switch (ProjectionPlane)
{
case EPlaneProjection::XZ:
Point2D = FVector2D(WorldLocation.X, WorldLocation.Z);
break;
case EPlaneProjection::YZ:
Point2D = FVector2D(WorldLocation.Y, WorldLocation.Z);
break;
case EPlaneProjection::XY:
default:
Point2D = FVector2D(WorldLocation.X, WorldLocation.Y);
break;
}
ProjectedPoints.Add(Point2D);
}
// Shoelace forumula. Inaccurate or just floating point problems?
float ShoelaceSum = 0.0f;
const int32 NumPoints = ProjectedPoints.Num();
for (int32 i = 0; i < NumPoints; ++i)
{
const FVector2D& CurrentPoint = ProjectedPoints[i];
const FVector2D& NextPoint = ProjectedPoints[(i + 1) % NumPoints];
ShoelaceSum += (CurrentPoint.X * NextPoint.Y) - (CurrentPoint.Y * NextPoint.X);
}
const float Area = 0.5f * FMath::Abs(ShoelaceSum);
return Area;
}