Calculate point B of the Pythagoras right-angled triangle (IMAGE)

Hi everyone,

I have the following situation:

I have:

  • Point A, already (hypotenuses)
  • P3, the player position Player.X, .Y (also the direction between A and Player.X, .Y)
  • P1 to P2 the Line (adjacent)
  • The length between point B and C must be the radius of the circle…
  • And we have the normals of the Line already.

How to I calculate Point B, if the the length of the opposite is exactly the radius?
Thank you…


This gives me the correct CENTER point of the line.

public double CircleLineSwept(ref PlayerStruc p, ref Point p1, ref Point p2, ref double normalX, ref double normalY)
{
    double dirX = p2.x - p1.x;
    double dirY = p2.y - p1.y;
    double length = Math.Sqrt(dirX * dirX + dirY * dirY);
    dirX /= length;
    dirY /= length;

    double x1 = p1.x;
    double y1 = p1.y;
    double x2 = p2.x;
    double y2 = p2.y;
    double x3 = p.x;
    double y3 = p.y;
    double x4 = p.x + p.dirX;
    double y4 = p.y + p.dirY;

    double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if (denom == 0)
    {
        return 1.0;
    }
    double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
    double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
    if (t >= 0 && t <= 1 && u >= 0)
    {
        double intersectionX = x1 + t * (x2 - x1);
        double intersectionY = y1 + t * (y2 - y1);
        normalX = intersectionX - p.x;
        normalY = intersectionY - p.y;
        length = Math.Sqrt(normalX * normalX + normalY * normalY);
        normalX /= length;
        normalY /= length;
        return u;
    }
    return 1.0;
}

First let’s calculate the normalized vector from P1 to P2:
P = P2 - P1 // tip - tail
P /= sqrt(dot(P, P))

Next let’s calculate the normalized vector from A to P3:
D = P3 - A
D /= sqrt(dot(D, D))

The dot product will give you the cosine of the angle between the two normalized vectors:
cos(alpha) = dot(P, D) ↔
alpha = acos(dot(P, D))

Now you have a triangle which three givens: The angle alpha, the opposite with length r (the radius) and the right angle. This means, you can calculate all the missing lengths and angles. We are interested in the hypotenuse c (length between A and B) which is calculated like this
sin(alpha) = r / c ↔
c = r / sin(alpha)

I think, this can be simplified because sin(acos(x)) = sqrt(1 - x * x):
tmp = dot(P, D)
c = r / sqrt(1 - tmp * tmp)

Given the length c, it’s now easy to calculate the center B:
B = A + c * D

Disclaimer: I haven’t actually checked any of this

You can solve this by getting the current radius of p3 to the line and scaling that to the desired radius.

Shadertoy (GLSL) code:

float sdSegment( in vec2 p, in vec2 a, in vec2 b ) // from: https://iquilezles.org/articles/distfunctions2d/
{ vec2 pa = p-a, ba = b-a; float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return length( pa - ba*h ); }

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Centered uv from -1 to 1
    vec2 uv = vec2((fragCoord.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * fragCoord.y / iResolution.y - 1.);
    vec2 muv = vec2((iMouse.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * iMouse.y / iResolution.y - 1.);
    float uvscale = 10.; uv *= uvscale, muv *= uvscale; // scale uv
 
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
    // Points
    vec2 p1 = vec2(-12.37, -7.65), p2 = vec2(15.64, 4.07); // line points
    vec2 p3 = vec2(2.8, 8.73); // player point
    vec2 A = p1 + 0.3 * (p2 - p1); // A
 
    // Project p3 onto line p1p2
    float t = dot(p3 - p1, normalize(p2 - p1)); // time of p3 on line p1p2
    vec2 D = p1 + t * normalize(p2 - p1); // projected point
 
    // Radius (input)
    float radius = iMouse.x < 1.5 ? 5. : 10. * iMouse.x / iResolution.x; // arbitrary value chosen by user
    radius = clamp(radius, 0., distance(p3, D)); // clamps radius to range of p3 to A; not necessary
 
    // Get scale by dividing the desired radius by the distance of p3 to the projected point (i.e. the "current" radius)
    float scale = radius / distance(p3, D);
 
    // Scale line *AD* (A to the projected point) by that value; this gives point C
    vec2 C = A + scale * (D - A);
 
    // Point B (output) is C offset by the normal of the line (normalized vector of D to p3) multiplied by the desired radius
    vec2 B = C + radius * normalize(p3 - D);
 
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
    // Pixel color
    vec3 col = vec3(1.);
 
    // Draw lines
    if (sdSegment(uv, p1, p2) < 0.01 * uvscale) col = vec3(0.);
    if (sdSegment(uv, p3, p3 + 1.3 * (A - p3)) < 0.01 * uvscale) col = vec3(1., 0., 0.);
    if (sdSegment(uv, C, B) < 0.01 * uvscale) col = vec3(0., 1., 0.);
 
    // Draw circle (proof)
    if (abs(distance(uv, B) - radius) < 0.01 * uvscale) col = vec3(0., 0., 1.);
 
    // Draw points
    //if (distance(uv, p1) < 0.03 * uvscale) col = vec3(0., 0., 0.);
    //if (distance(uv, p2) < 0.03 * uvscale) col = vec3(0., 0., 0.);
    //if (distance(uv, p3) < 0.03 * uvscale) col = vec3(1., 0., 0.);
    if (distance(uv, A) < 0.03 * uvscale) col = vec3(1., 0., 0.);
    //if (distance(uv, D) < 0.03 * uvscale) col = vec3(1., 0., 0.);
    if (distance(uv, C) < 0.03 * uvscale) col = vec3(0., 1., 0.);
    if (distance(uv, B) < 0.03 * uvscale) col = vec3(0., 0., 1.);
 
    // Output to screen
    fragColor = vec4(col, 1.);
}

Result

If you don’t know what to do with this, just paste it into https://www.shadertoy.com/new to view & play with it. Moving the mouse left & right will change the radius.

For the code itself, you only need what’s in the commented bars. It’s in GLSL, not C#, but the important part is the math, so it should be easy to convert. It should also work in 3D.

For those who would like to know, here’s the logic behind it:

If we project a point we already know onto line p1p2 (e.g. p3), we will get a projected point on the line with a distance to the original point. If we treat this distance as a radius, then we now have an undesired point (p3) at an undesired radius (distance of p3 to the projected point). So, all we have to do is convert that undesired radius to a desired one. We can do this by scaling the projected point along line p1p2 such that the radius (i.e. the distance from point C to line p3A) is equal to our desired one. Point B is then obtained by simply offsetting point C by the line’s “normal” (normalized vector of the projected point to p3) scaled by the desired radius.

Nice @UhOhItsMoving ! You used a completely different approach.

I was curious to see whether my math worked as well so I took the liberty to plug it into your Shadertoy code (it does).

float sdSegment( in vec2 p, in vec2 a, in vec2 b ) // from: https://iquilezles.org/articles/distfunctions2d/
{ vec2 pa = p-a, ba = b-a; float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return length( pa - ba*h ); }

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Centered uv from -1 to 1
    vec2 uv = vec2((fragCoord.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * fragCoord.y / iResolution.y - 1.);
    vec2 muv = vec2((iMouse.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * iMouse.y / iResolution.y - 1.);
    float uvscale = 10.; uv *= uvscale, muv *= uvscale; // scale uv

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Points
    vec2 p1 = vec2(-12.37, -7.65), p2 = vec2(15.64, 4.07); // line points
    vec2 p3 = vec2(2.8, 8.73); // player point
    vec2 A = p1 + 0.3 * (p2 - p1); // A

    // Radius (input)
    float radius = iMouse.x < 1.5 ? 5. : 10. * iMouse.x / iResolution.x; // arbitrary value chosen by user

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // BEGIN c0d3_m0nk3y

    // First let's calculate the normalized vector from P1 to P2:
    vec2 P = normalize(p2 - p1);

    // Next let's calculate the normalized vector from A to P3:
    vec2 D = normalize(p3 - A);

    // Calculate length of hypotenuse (distance from A to B)
    float tmp = dot(P, D);
    float c = radius / sqrt(1.0 - tmp * tmp);

    // Given the length c, it's now easy to calculate the center B:
    vec2 B = A + c * D;
  
    // END c0d3_m0nk3y
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
    // Calculate C only for visualization
    vec2 C = B + radius * vec2(P.y, -P.x);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Pixel color
    vec3 col = vec3(1.);

    // Draw lines
    if (sdSegment(uv, p1, p2) < 0.01 * uvscale) col = vec3(0.);
    if (sdSegment(uv, p3, p3 + 1.3 * (A - p3)) < 0.01 * uvscale) col = vec3(1., 0., 0.);
    if (sdSegment(uv, C, B) < 0.01 * uvscale) col = vec3(0., 1., 0.);

    // Draw circle (proof)
    if (abs(distance(uv, B) - radius) < 0.01 * uvscale) col = vec3(0., 0., 1.);

    // Draw points
    if (distance(uv, A) < 0.03 * uvscale) col = vec3(1., 0., 0.);
    if (distance(uv, C) < 0.03 * uvscale) col = vec3(0., 1., 0.);
    if (distance(uv, B) < 0.03 * uvscale) col = vec3(0., 0., 1.);

    // Output to screen
    fragColor = vec4(col, 1.);
}

Thank you!.. with this ideas I have extend the approach to a CircleLineCollision function. The player walks exactly along the right edge. But the code unfortunately extents the left point of the collider-line like : PLeft -= seglen. :face_with_spiral_eyes: So it go wrong at the left point. The right point works perfekt! I need double precision calculations… this is so amazing fast.

The code utilizes an offset line, which is calculated based on the player’s collider size. This offset line is parallel to the original line segment and serves as an extended boundary for collision detection with the player’s circular collider.
It checks if the player’s movement vector intersects with the offset line within the segment boundaries. If there is an intersection and the distance between the intersection point and the left collider point is within the segment length, it determines that a collision has occurred and returns the intersection point as the collision point.
If the player is coming from below the line segment, indicating no collision, it returns the player’s next position without any adjustments. Overall, the code handles collision detection and response for a player moving in a 2D space with line obstacles, utilizing an offset line concept to accurately detect and handle collisions.

using UnityEngine;

public struct PointLineD
{
    public double x1;
    public double y1;
    public double x2;
    public double y2;
}

public struct PointD
{
    public double x;
    public double y;
}

public struct PlayerStruct
{
    public double x;
    public double y;
    public double dirx;
    public double diry;
    public double colliderSize;
    public int NearestLine;
}

public Vector2 CircleLineCollision(ref PlayerStruct player, ref PointD p1, ref PointD p2)
{
    double ix, iy, nx, ny, dx, dy, d, a, b, t;
    Vector2 result = Vector2.zero;
    PointLineD pl = new PointLineD();

    // Calculate the offset line
    double segLen, ox, oy;
    dx = p2.x - p1.x;
    dy = p2.y - p1.y;
    segLen = Mathf.Sqrt((float)(dx * dx + dy * dy));
    ox = dy * player.colliderSize / segLen;
    oy = -dx * player.colliderSize / segLen;
    pl.x1 = p1.x + ox;
    pl.y1 = p1.y + oy;
    pl.x2 = p2.x + ox;
    pl.y2 = p2.y + oy;

    // Calculate the normals of the offset line
    nx = pl.y2 - pl.y1;
    ny = pl.x1 - pl.x2;

    // Calculate the difference between the player's position and the first point of the offset line
    dx = player.x - pl.x1;
    dy = player.y - pl.y1;

    // Project the player vector onto the normals of the offset line
    d = nx * dx + ny * dy;

    // Case: Player is coming from below
    if (d < 0.0)
    {
        result.x = (float)(player.x + player.dirx);
        result.y = (float)(player.y + player.diry);
        return result;
    }

    a = (pl.y2 - pl.y1) * player.dirx - (pl.x2 - pl.x1) * player.diry;
    if (a != 0.0)
    {
        b = (pl.x1 - player.x) * (pl.y2 - pl.y1) - (pl.y1 - player.y) * (pl.x2 - pl.x1);
        t = b / a;
        if (t >= 0.0 && t <= 1.0)
        {
            ix = player.x + t * player.dirx;
            iy = player.y + t * player.diry;

            dx = ix - pl.x1;
            dy = iy - pl.y1;

            // !!! **************************************************************************************
            // !!! here is the error!!!! why the heck is the left collider point position wrong only..
            // !!! **************************************************************************************
            if (Mathf.Sqrt((float)(dx * dx + dy * dy)) <= segLen)
            {
                result.x = (float)ix;
                result.y = (float)iy;
                return result;
            }
        }
    }

    // Case: Player passes by the offset line
    result.x = (float)(player.x + player.dirx);
    result.y = (float)(player.y + player.diry);
    return result;
}

I know it’s difficultly to find out what math cause the problem here… but perhaps you have an idea…
thanks a lot

Are you extending the left point to the left and the right point to the right (e.g. similar to scaling the line from the center)? Because I think you’re offsetting both points to the same side rather than extending them to both sides.