Working out the area of a spline loop, do I need to use shoelace formula and 2D projection?

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;
}

1 Like