Move point B to be between A and C while keeping the distance

63 Views Asked by At

Am looking for a way to trying to perform this change:

enter image description here

A and C are fixed 3D points. B shall then be moved so that AtoB and CtoB have the average distance of what they used to have.

Sounds trivial somehow but the best I got so far is this which ends up flipping the point and somewhat averages if executed multiple times.

    Vector3 dir_a = B - A;
    Vector3 dir_b = B - C;

    float averageDistance = (dir_a.magnitude + dir_b.magnitude) / 2;

    dir_a.Normalize();
    dir_b.Normalize();

    B -= (averageDistance) * (dir_a + dir_b);

Somebody have a hint in the right direction?

1

There are 1 best solutions below

0
harold On

It's a bit ambiguous since there is usually a whole ring of valid solutions, but we can choose a point close to the original B (which has one issue which I address at the end). One way to do that, is to create a plane halfway between A and C perpendicular to the line between A and C, then starting on the intersection of that line and that plane, move towards B while staying on the plane until the distance is right. There is no search by the way, we can just calculate the required radius: we'll have a right-angle triangle where we are given the base, and we need to pick a height such that the hypotenuse has the desired length.

Like this:

    Vector3 dir_a = B - A;
    Vector3 dir_b = B - C;
    float avg_d = (dir_a.Length() + dir_b.Length()) / 2;
    // axis is the normal vector of the plane between A and C
    // perpendicular to the line between A and C
    Vector3 axis = Vector3.Normalize(C - A);
    // point halfway between A and C,
    // will be the origin of the circle on which the result lies,
    // and it will be where the right angle of the right angle triangle is
    Vector3 midpoint = (A + C) / 2;
    Vector3 mid_to_b = B - midpoint;
    // project mid_to_B onto the plane and normalize,
    // forming the direction of the height of the triangle (radius of the circle)
    Vector3 dir = Vector3.Normalize(mid_to_b - Vector3.Dot(axis, mid_to_b) * axis);
    // the triangle should satisfy:
    // height² + base² = avg_d²
    // where the base is the distance between the midpoint and C (or A, same thing)
    // solving for height:
    // height² = avg_d² - base²
    // height = sqrt(avg_d² - base²)
    float axis_length2 = (C - midpoint).LengthSquared();
    float height = MathF.Sqrt(avg_d * avg_d - axis_length2);
    // go from the midpoint in the right direction for the distance we just calculated:
    Vector3 result = midpoint + dir * height;

Note that if A, B, and C, are collinear, then this approach doesn't work. If you care about that, you could detect that and try to use another vector instead of mid_to_b in that case. Since this approach tries to go in the direction of B, if that direction is perpendicular to the plane then no movement is possible. dir may become NaN or (thanks to floating point stuff making the vector being normalized only almost zero instead of actually zero) some not particularly useful direction.

You can use any direction orthogonal to axis, ie Vector3 dir = Vector3.Normalize(Orthogonal(axis)) and you can get some orthogonal vector like this: (if you're not super picky about what direction you get, just that it's orthogonal, and here I pick the longest option mainly to avoid accidentally getting a zero vector)

    static Vector3 Orthogonal(Vector3 dir)
    {
        Vector3 A = new Vector3(0, dir.Z, -dir.Y);
        Vector3 B = new Vector3(-dir.Z, 0, dir.X);
        Vector3 C = new Vector3(-dir.Y, dir.X, 0);
        float lenA = A.LengthSquared();
        float lenB = B.LengthSquared();
        float lenC = C.LengthSquared();
        if (lenA > lenB) 
        {
            // make B the largest vector among A and B
            B = A;
            lenB = lenA;
        }
        // now one of B or C must be the longest
        if (lenB > lenC)
            return B;
        else
            return C;
    }

Using that is more robust, but:

  • This direction is independent of B, sending the new point off into some direction unrelated to B, the only thing B would be used for is initially finding avg_d.
  • This costs a bunch more code/complications/probably also time.