How to find Relative Offset of a point inside a non axis aligned box (box that is arbitrarily rotated)

731 Views Asked by At

I'm trying to solve an problem where I cannot find the Relative Offset of a Point inside a Box that exists inside of a space that can be arbitrarily rotated and translated.

I know the WorldSpace Location of the Box (and its 4 Corners, the Coordinates on the Image are Relative) as well as its Rotation. These can be arbitrary (its actually a 3D Trigger Volume within a game, but we are only concerned with it in a 2D plane from top down).

enter image description here

Looking at it Aligned to an Axis the Red Point Relative position would be

0.25, 0.25

If the Box was to be Rotated arbitrarily I cannot seem to figure out how to maintain that given we sample the same Point (its World Location will have changed) its Relative Position doesnt change even though the World Rotation of the Box has.

enter image description here

For reference, the Red Point represents an Object that exists in the scene that the Box is encompassing.

bool UPGMapWidget::GetMapMarkerRelativePosition(UPGMapMarkerComponent* MapMarker, FVector2D& OutPosition)
{
    bool bResult = false;
    if (MapMarker)
    {
        const FVector MapMarkerLocation = MapMarker->GetOwner()->GetActorLocation();
        float RelativeX = FMath::GetMappedRangeValueClamped(
            -FVector2D(FMath::Min(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerBottomRightLocation().X), FMath::Max(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerBottomRightLocation().X)),
            FVector2D(0.f, 1.f),
            MapMarkerLocation.X
        );

        float RelativeY = FMath::GetMappedRangeValueClamped(
            -FVector2D(FMath::Min(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomRightLocation().Y), FMath::Max(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomRightLocation().Y)),
            FVector2D(0.f, 1.f),
            MapMarkerLocation.Y
        );
        
        OutPosition.X = FMath::Abs(RelativeX);
        OutPosition.Y = FMath::Abs(RelativeY);

        bResult = true;
    }

    return bResult;
}

Currently, you can see with the above code that im only using the Top Left and Bottom Right corners of the Box to try and calculate the offset, I know this is not a sufficient solution as doing this does not allow for Rotation (Id need to use the other 2 corners as well) however I cannot for the life of me work out what I need to do to reach the solution.

FMath::GetMappedRangeValueClamped

This converts one range onto another. (20 - 50) becomes (0 - 1) for example.

Any assistance/advice on how to approach this problem would be much appreciated.

Thanks.

UPDATE

@Voo's comment helped me realize that the solution was much simpler than anticipated.

enter image description here

By knowing the Location of 3 of the Corners of the Box, I'm able to find the points on the 2 lines these 3 Locations create, then simply mapping those points into a 0-1 range gives the appropriate value regardless of how the Box is Translated.

bool UPGMapWidget::GetMapMarkerRelativePosition(UPGMapMarkerComponent* MapMarker, FVector2D& OutPosition)
{
    bool bResult = false;
    if (MapMarker && GetMapVolume())
    {
        const FVector MapMarkerLocation = MapMarker->GetOwner()->GetActorLocation();
        const FVector TopLeftLocation = GetMapVolume()->GetCornerTopLeftLocation();
        const FVector TopRightLocation = GetMapVolume()->GetCornerTopRightLocation();
        const FVector BottomLeftLocation = GetMapVolume()->GetCornerBottomLeftLocation();

        FVector XPlane = FMath::ClosestPointOnLine(TopLeftLocation, TopRightLocation, MapMarkerLocation);
        FVector YPlane = FMath::ClosestPointOnLine(TopLeftLocation, BottomLeftLocation, MapMarkerLocation);

        // Convert the X axis into a 0-1 range.
        float RelativeX = FMath::GetMappedRangeValueUnclamped(
            FVector2D(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerTopRightLocation().X),
            FVector2D(0.f, 1.f),
            XPlane.X
        );

        // Convert the Y axis into a 0-1 range.
        float RelativeY = FMath::GetMappedRangeValueUnclamped(
            FVector2D(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomLeftLocation().Y),
            FVector2D(0.f, 1.f),
            YPlane.Y
        );

        OutPosition.X = RelativeX;
        OutPosition.Y = RelativeY;

        bResult = true;
    }

    return bResult;
}

The above code is the amended code from the original question with the correct solution.

1

There are 1 best solutions below

0
jhcarl0814 On

assume the origin is at (x0, y0), the other three are at (x_x_axis, y_x_axis), (x_y_axis, y_y_axis), (x1, y1), the object is at (x_obj, y_obj)

do these operations to all five points:
(1)translate all five points by (-x0, -y0), to make the origin moved to (0, 0) (after that (x_x_axis, y_x_axis) is moved to (x_x_axis - x0, y_x_axis - y0));
(2)rotate all five points around (0, 0) by -arctan((y_x_axis - y0)/(x_x_axis - x0)), to make the (x_x_axis - x0, y_x_axis - y0) moved to x_axis;
(3)assume the new coordinates are (0, 0), (x_x_axis', 0), (0, y_y_axis'), (x_x_axis', y_y_axis'), (x_obj', y_obj'), then the object's zero-one coordinate is (x_obj'/x_x_axis', y_obj'/y_y_axis');

rotate formula:(x_new, y_new)=(x_old * cos(theta) - y_old * sin(theta), x_old * sin(theta) + y_old * cos(theta))

Update:
Note:

  1. If you use the distance method, you have to take care of the sign of the coordinate if the object might go out of the scene in the future;
  2. If there will be other transformations on the scene in the future (like symmetry transformation if you have mirror magic in the game, or transvection transformation if you have shockwaves, heatwaves or gravitational waves in the game), then the distance method no longer applies and you still have to reverse all the transformations your scene has in order to get the object's coordinate.