Math question. finding distance to the edge of a plane in direction from center.

I’m either really tired or missing some basic maths.
I need to get the distance from the center of a rectangle or square to an intersection on one of its edges.
The variable here is the direction we use starting from the center.
The extent of X and Y of the plane is known, it has no weird shape, so perhaps there’s some way to interpolate a radius from corner to corner.

What is the basic formula for this?

From some old project I remember using a line intersect method probably in the Kismet libs but it would fail on corners because of float precision.

Any way to improve over this?

// Check for point intersections. Works with float precision reliably.
for (const TPair<int32, FDPoint>& PointX : Points) {
	if (FMath::IsNearlyZero(FMath::PointDistToLine(PointX.Value.GetXYZ(), SliceDirection, SliceStart))) {
		OutSlicePoints.Add(PointX.Key);
	}
}

// Check for edge intersections. Does not work well for corner points (float precision.)
for (const FIntPoint& EdgeIdsX : Edges) {
	// Get the points which belong to this edge.
	const FDPoint& P1 = Points.FindChecked(EdgeIdsX.X);
	const FDPoint& P2 = Points.FindChecked(EdgeIdsX.Y);

	// For whatever reason the 2D function in the segment intersection method takes 3D vectors.
	const FVector SliceEnd = SliceStart + (SliceDirection * KnifeLength);
	const FVector EdgeXP1Pos = FVector(P1.X, P1.Y, 0.f);
	const FVector EdgeXP2Pos = FVector(P2.X, P2.Y, 0.f);
	FVector OutIntersectPoint = FVector::ZeroVector;
	if (FMath::SegmentIntersection2D(SliceStart, SliceEnd, EdgeXP1Pos, EdgeXP2Pos, OutIntersectPoint)) {
		OutSliceEdges.Add(EdgeIdsX, OutIntersectPoint);
	}
}

I believe I’m looking for something like this:

polar coordinates vector equation of a rectangle - Mathematics Stack Exchange

But I’m not used to the “math language”, looking for a readable explanation, I can’t read these symbols.

Hi. Could you draw picture which describes task?
Do I understand correctly that there are 3D plane (which is bounded by 4 3D lines) and in-plane vector from center of plane, and you need to find distance from center to cross point between this vector and plane border?
Probably there is UE engine solution, but here is my understanding how to solve problem:

  1. You need to find cross point of vector and 4 plane sides;
  2. In 3D there may be no cross points (due floating point errors);
  3. So, I suggest to project vector into plane basis. You will get vector coordinates in plane coord system. Plane sides also should be converted into 2D plane space, but it is easy - you have plane width and height (just use plane sides as coordinate system basis and project vector to each side);
  4. Now you need to solve simple equations for your vector. Find cross point for each side. In common case (if vector is not parallel to any axis) vector will cross all sides, but two of them will be crossed in wrong direction. Take nearest cross point from other two.
  5. Algorithm may be optimized. We have symmetric plane, so we need to find cross points for two sides and take nearest.
1 Like

Not sure if I get the question right (drawing would help), how about this

// FVector PlaneDirX, PlaneDirY
// FVector2D PlaneExtent
// FVector Direction

// Get the contribution of Direction to each side of the plane (avoiding zero edge case)
FVector2D Projected = FVector2D(
   FMath::Max(0.01f, FMath::Abs(Direction.Dot(PlaneDirX))),
   FMath::Max(0.01f, FMath::Abs(Direction.Dot(PlaneDirY)))
).GetSafeNormal();  //normalize in case Direction was not exactly in-plane

// Use division to calculate the amount of times we can fit each component into Extent/2
FVector2D Dist = (PlaneExtent / 2) / Projected;

// Return nearest one
return Dist.GetMin();

Looking at your blueprint you have a rotator for the plane orientation. You should be able to get plane axis directly from that rotator :

FVector PlaneDirX = Rotation.GetForwardVector();
FVector PlaneDirY = Rotation.GetRightVector();

(might have to replace one of those with GetUpVector() depending on how the orientation of the platform is set up)

1 Like

@Chatouille ,@PazDim thanks for answering :slight_smile: I will explain it with a drawing. The plane is in 2D space, the Z is constant for all points.

This is known:

  • The extent of a plane.
  • The center point of the plane (always exact center).
  • The angle we use to measure distance from center to edge from center.
  • The orientation is Z up, a constant we can 0 out.

afbeelding

The plane is made from an extent, so we can say X == 4 and Y == 2, to get 4 points in 2D space (2, -2, 1, -1) (brown edges).

The center (green) is always 0.

If we input the direction from the center outward that we want to measure (yellow), be it 45 degrees, I need a method to return me the distance to the edge it points at.

@Chatouille this “direction” is the yaw from the relative rotation of a component on my blueprint screenshot, which represents the plane.

Apparently there is something in maths called a “polar coordinates vector equation” which would do this quick without having to test for

I tried checking for intersections on all edges in the code i posted earlier, but because of float precision you need to add additional checks for corner points and can miss a line entirely. In this situation the right math will probably be the more reliable option.

The use case for this, is that I have a rectangle shaped platform (not a mesh, no collision, just a virtual area) which at some point checks if certain actors are still on the platform. If not, it pulls them in towards the edge and places them on the edge. To do so I get the distance from the center point of the platform to the actors registered to the platform and compare this distance to the distance from the center point of the platform to its edge in the same direction. If the distance to the actor is greater, it is outside the platform and its location is set to the edge position. If done on tick, this must cleanly keep actors on the edge or within the plane.

afbeelding

For reasons, a collision setup to keep actors in was not desirable here.

In the meantime I’m going to test your posts in the editor

As alternative this draws a line to the center when the point is not inside:

FVector AEdgeIntersection::BoxIntersection(
	UPARAM(ref) const FVector& BoxExtent3D,
	UPARAM(ref) const FTransform& BoxTransform,
	UPARAM(ref) const FVector& LineStart)
{
	const bool bInBox = UKismetMathLibrary::IsPointInBox(LineStart, BoxTransform.GetLocation(), BoxExtent3D);

	if (!bInBox)
	{
		// FBox do not rotate so we rotate the start of the line.
		const FVector TransformedLineStart = BoxTransform.Inverse().TransformVector(LineStart - BoxTransform.GetLocation()) + BoxTransform.GetLocation();
		FVector BoxSweepExtent = FVector::ZeroVector;
		FVector HitLocation;
		FVector HitNormal;
		float HitTime;
		FBox Box = FBox::BuildAABB(BoxTransform.GetLocation(), BoxExtent3D);
		// Get edge intersection.
		FMath::LineExtentBoxIntersection(Box, TransformedLineStart, BoxTransform.GetLocation(), BoxSweepExtent, HitLocation, HitNormal, HitTime);
		
		// Location on bounds with rotation.
		const FVector ProjectedHitLocation = BoxTransform.TransformVector(HitLocation - BoxTransform.GetLocation()) + BoxTransform.GetLocation();

		// FBox with no rotation.
		DrawDebugBox(GetWorld(), BoxTransform.GetLocation(), BoxExtent3D, FColor::Yellow, false, -1.f, 0, 2.f);
		// Box with rotation
		DrawDebugBox(GetWorld(), BoxTransform.GetLocation(), BoxExtent3D, BoxTransform.GetRotation(), FColor::Red, false, -1.f, 0, 2.f);
		// Projected on rotated box.
		DrawDebugSphere(GetWorld(), ProjectedHitLocation, 10.f, 16, FColor::Red, false, -1.f, 0, 2.f);
		// Aligned with the FBox used for bounds.
		DrawDebugSphere(GetWorld(), HitLocation, 10.f, 16, FColor::Yellow, false, -1.f, 0, 2.f);

		return ProjectedHitLocation;
	}
	return FVector::ZeroVector;
}

BoxEdge

3 Likes

Thank you :slight_smile:
meanwhile I set up a base template with the idea in blueprint
PlaneEdgeMeasure.zip (39.5 KB)

I need some time to test, I’ll be back later :slight_smile:

This doesn’t seem right, or I mess up somewhere. I used blueprint nodes for the time since compiling c++ takes ages lately.

PlaneEdgeMeasure-Chatouille.zip (52.0 KB)

Hey @Roy_Wierer.Seda145

here is an example :slightly_smiling_face:

UE52_PEM_V1.zip (109.3 KB)

Only blueprints.

1 Like

Aww thanks a lot that is very well made. This would be the perfect answer if you have a rectangle which does not have a rotator. The calculations used work only if the rectangle is at a 0 rotation. If this was the case this would be the answer but sadly I must be able to use it on any rotation angle. I noticed a small detail in the demo it is missing a zero division guard, especially when one of the actors measured is at position 0.

Even though it’s for a box, this works incredibly well. I like it. I’m checking a few things then I’ll upload a blueprint function library version. Great work :slightly_smiling_face:

This works when measuring from the center to a point outward and works very well. Exactly what I need and asked for haha.

Usage demo based on an improved version of the template I updated earlier, method Pezzott1 made is included as a blueprint function library, I changed a few parameter names. Demo is set up to test intersections for any plane transform by any rotation step over tick.

Thanks all for the input, this is great. :smiley:

2 Likes

Well, here is my V2, if someone is interested :slight_smile:

UE52_PEM_V2.zip (119.8 KB)

3 Likes

haha you are amazing. Technically this is the most fitting answer since it now works with a rotated 2D plane. it’s compact as well. Some day I will learn the magic of those Sin and Cos functions I swear :wink:

This is the most efficient answer.
For box Pezzot1 has the answer.

I’m sure I’ll use them both.

3 Likes

Removed old files, port of both solutions to c++ with fix for return values over my previous upload.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"

#include "IntersectionUtils.generated.h"


class UWorld;


/**
 * 
 */
UCLASS()
class PEM_V1_API UIntersectionUtils : public UBlueprintFunctionLibrary {
	GENERATED_BODY()

public:

	UFUNCTION(BlueprintCallable, Category = "Utils|Intersection")
	static bool GetIntersectionFromBoxCenterToEdge(const FVector& InPointToTest, const FVector& InBoxExtent3D, const FTransform& InBoxTransform, FVector& OutIntersectionPoint, const UObject* const InDebugWorldContext = nullptr);

	UFUNCTION(BlueprintCallable, Category = "Utils|Intersection")
	static bool GetIntersectionFrom2DPlaneCenterToEdge(const FVector2D& InPointToTest, const FVector2D& InPlaneLocation, const FVector2D& InPlaneExtent, float InPlaneYaw, FVector2D& OutIntersectionPoint, const UObject* const InDebugWorldContext = nullptr);
	
};

#include "IntersectionUtils.h"
#include "Kismet/KismetMathLibrary.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"


bool UIntersectionUtils::GetIntersectionFromBoxCenterToEdge(const FVector& InPointToTest, const FVector& InBoxExtent3D, const FTransform& InBoxTransform, FVector& OutIntersectionPoint, const UObject* const InDebugWorldContext) {
	const FVector CenterPoint = InBoxTransform.GetLocation();
	const UWorld* InDebugWorld = IsValid(InDebugWorldContext) ? InDebugWorldContext->GetWorld() : nullptr;
	const FVector TransformedPointToTest = InBoxTransform.Inverse().TransformVector(InPointToTest - CenterPoint) + CenterPoint;
	const bool bInBox = UKismetMathLibrary::IsPointInBox(TransformedPointToTest, CenterPoint, InBoxExtent3D);
	const bool bPersistDebug = false;
	const float DebugLifetime = -1.f;

	if (!bInBox) {
		FVector HitLocation = FVector::ZeroVector;;
		// FBox do not rotate so we rotate the CenterPoint.
		const FVector BoxSweepExtent = FVector::ZeroVector;
		const FBox Box = FBox::BuildAABB(CenterPoint, InBoxExtent3D);
		FVector HitNormal;
		float HitTime;
		// Get edge intersection.
		FMath::LineExtentBoxIntersection(Box, TransformedPointToTest, CenterPoint, BoxSweepExtent, HitLocation, HitNormal, HitTime);

		// Location on bounds with rotation.
		OutIntersectionPoint = InBoxTransform.TransformVector(HitLocation - CenterPoint) + CenterPoint;

		if (IsValid(InDebugWorld)) {
			// Hit location aligned with the FBox used for bounds.
			DrawDebugSphere(InDebugWorld, HitLocation, 30, 16, FColor::Orange, bPersistDebug, DebugLifetime, 0, 15.f);
			// Hit location projected on rotated box.
			DrawDebugSphere(InDebugWorld, OutIntersectionPoint, 30.f, 16, FColor::Red, bPersistDebug, DebugLifetime, 0, 15.f);
		}
	}

	if (IsValid(InDebugWorld)) {
		// FBox with no rotation.
		DrawDebugBox(InDebugWorld, CenterPoint, InBoxExtent3D, FColor::White, bPersistDebug, DebugLifetime, 0, 4.f);
		// Box with rotation
		DrawDebugBox(InDebugWorld, CenterPoint, InBoxExtent3D, InBoxTransform.GetRotation(), FColor::Yellow, bPersistDebug, DebugLifetime, 0, 4.f);
		// CenterPoint
		DrawDebugSphere(InDebugWorld, CenterPoint, 30.f, 16, FColor::White, bPersistDebug, DebugLifetime, 0, 30.f);
		if (bInBox) {
			// TransformedPointToTest
			DrawDebugSphere(InDebugWorld, TransformedPointToTest, 30.f, 16, FColor::Cyan, bPersistDebug, DebugLifetime, 0, 30.f);
		}
	}

	return bInBox == false;
}


bool UIntersectionUtils::GetIntersectionFrom2DPlaneCenterToEdge(const FVector2D& InPointToTest, const FVector2D& InPlaneLocation, const FVector2D& InPlaneExtent, float InPlaneYaw, FVector2D& OutIntersectionPoint, const UObject* const InDebugWorldContext) {
	const bool bPersistDebug = false;
	const float DebugLifetime = 3.f;
	const float DCosYaw = UKismetMathLibrary::DegCos(InPlaneYaw);
	const float DSinYaw = UKismetMathLibrary::DegSin(InPlaneYaw);

	// Add the location offset of the plane.
	const FVector2D MovedPointToTest = InPointToTest - InPlaneLocation;
	// Rotate to the planes rotation.
	const FVector2D TransformedPointToTest = FVector2D(
		(DCosYaw * MovedPointToTest.X) + (DSinYaw * MovedPointToTest.Y)
		, (DCosYaw * MovedPointToTest.Y) - (DSinYaw * MovedPointToTest.X)
	);
	
	// If within bounds, we dont get an intersection. Then we should return.
	if (FMath::Abs(TransformedPointToTest.X) <= InPlaneExtent.X
		&& FMath::Abs(TransformedPointToTest.Y) <= InPlaneExtent.Y
		) {
		OutIntersectionPoint = FVector2D::ZeroVector;
		return false;
	}

	const FVector2D TransformedPointWithinBounds = TransformedPointToTest / (
		// Check if inside bounds.
		FMath::Abs((TransformedPointToTest / (FMath::Abs(TransformedPointToTest.X) / InPlaneExtent.X)).Y) <= InPlaneExtent.Y
		? FMath::Abs(TransformedPointToTest.X) / InPlaneExtent.X
		: FMath::Abs(TransformedPointToTest.Y) / InPlaneExtent.Y
	);

	OutIntersectionPoint = FVector2D(
		(UKismetMathLibrary::DegCos(InPlaneYaw * -1) * TransformedPointWithinBounds.X) + (UKismetMathLibrary::DegSin(InPlaneYaw * -1) * TransformedPointWithinBounds.Y)
		, (UKismetMathLibrary::DegCos(InPlaneYaw * -1) * TransformedPointWithinBounds.Y) - (UKismetMathLibrary::DegSin(InPlaneYaw * -1) * TransformedPointWithinBounds.X)
	) + InPlaneLocation;

	const UWorld* InDebugWorld = IsValid(InDebugWorldContext) ? InDebugWorldContext->GetWorld() : nullptr;
	if (IsValid(InDebugWorld)) {
		// Line from plane center to point to test (original)
		DrawDebugLine(InDebugWorld, FVector(InPlaneLocation, 0.f), FVector(InPointToTest, 0.f), FColor::White, bPersistDebug, DebugLifetime, 0, 10.f);
		// Box with rotation
		DrawDebugBox(InDebugWorld, FVector(InPlaneLocation, 0.f), FVector(InPlaneExtent, 1.f), FRotator(0.f, InPlaneYaw, 0.f).Quaternion(), FColor::Yellow, bPersistDebug, DebugLifetime, 0, 15.f);
		// Transformed intersect point
		DrawDebugSphere(InDebugWorld, FVector(OutIntersectionPoint, 0.f), 50.f, 16, FColor::Red, bPersistDebug, DebugLifetime, 0, 20.f);
	}

	return true;
}

*Edit Fixed bug where UKismetMathLibrary::IsPointInBox did not use the transformed InPointToTest, basically causing the check to be done on the unrotated box.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.