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

Quick question, I’m trying to work out the area of a closed spline loop in 3D space. I’m coming across all kinds of complex methods for working out areas of n-sided polygons in 3D space and using the shoelace method. Is this really necessary? I thought I could simply calculate the area from the lengths of each spline segment?

I found this:

Does it matter whether it’s a flat spline on a 2D plane or spline oriented in any direction in 3D space?

Hey there @Anthony_Attwood! That BP is a pretty good setup for the Shoelace method, and if you need the data as a 2D projection, that’s perfect. However if the shape is more than just an XY plane, this gets more complicated.

I’m a bit noob when it comes to maths so I made a shape and used the blueprint above for the area calculation. The area is in the middle there. Is this the correct answer?

If not I think maybe it’s inaccurate.

The numbers are:

278.847 (left)

230.308 (right)

361.694 (base)

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

Wooah this is incredible! I’ll back the project up and set it up for C++ and give this a try. Thank you so much for this. I’ll give an update as soon as possible.

1 Like