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

Discussion in 'General Graphics' started by Quatum1000, Jun 2, 2023.

1. ### Quatum1000

Joined:
Oct 5, 2014
Posts:
888
Hi everyone,

I have the following situation:

I have:
+ 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.
Code (CSharp):
1. public double CircleLineSwept(ref PlayerStruc p, ref Point p1, ref Point p2, ref double normalX, ref double normalY)
2. {
3.     double dirX = p2.x - p1.x;
4.     double dirY = p2.y - p1.y;
5.     double length = Math.Sqrt(dirX * dirX + dirY * dirY);
6.     dirX /= length;
7.     dirY /= length;
8.
9.     double x1 = p1.x;
10.     double y1 = p1.y;
11.     double x2 = p2.x;
12.     double y2 = p2.y;
13.     double x3 = p.x;
14.     double y3 = p.y;
15.     double x4 = p.x + p.dirX;
16.     double y4 = p.y + p.dirY;
17.
18.     double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
19.     if (denom == 0)
20.     {
21.         return 1.0;
22.     }
23.     double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
24.     double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
25.     if (t >= 0 && t <= 1 && u >= 0)
26.     {
27.         double intersectionX = x1 + t * (x2 - x1);
28.         double intersectionY = y1 + t * (y2 - y1);
29.         normalX = intersectionX - p.x;
30.         normalY = intersectionY - p.y;
31.         length = Math.Sqrt(normalX * normalX + normalY * normalY);
32.         normalX /= length;
33.         normalY /= length;
34.         return u;
35.     }
36.     return 1.0;
37. }

2. ### c0d3_m0nk3y

Joined:
Oct 21, 2021
Posts:
532
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

Last edited: Jun 2, 2023
3. ### UhOhItsMoving

Joined:
May 25, 2022
Posts:
67
You can solve this by getting the current radius of p3 to the line and scaling that to the desired radius.

Code (CSharp):
1. float sdSegment( in vec2 p, in vec2 a, in vec2 b ) // from: https://iquilezles.org/articles/distfunctions2d/
2. { 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 ); }
3.
4. void mainImage( out vec4 fragColor, in vec2 fragCoord )
5. {
6.     // Centered uv from -1 to 1
7.     vec2 uv = vec2((fragCoord.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * fragCoord.y / iResolution.y - 1.);
8.     vec2 muv = vec2((iMouse.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * iMouse.y / iResolution.y - 1.);
9.     float uvscale = 10.; uv *= uvscale, muv *= uvscale; // scale uv
10.
11.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
12.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
13.
14.     // Points
15.     vec2 p1 = vec2(-12.37, -7.65), p2 = vec2(15.64, 4.07); // line points
16.     vec2 p3 = vec2(2.8, 8.73); // player point
17.     vec2 A = p1 + 0.3 * (p2 - p1); // A
18.
19.     // Project p3 onto line p1p2
20.     float t = dot(p3 - p1, normalize(p2 - p1)); // time of p3 on line p1p2
21.     vec2 D = p1 + t * normalize(p2 - p1); // projected point
22.
24.     float radius = iMouse.x < 1.5 ? 5. : 10. * iMouse.x / iResolution.x; // arbitrary value chosen by user
25.     radius = clamp(radius, 0., distance(p3, D)); // clamps radius to range of p3 to A; not necessary
26.
27.     // Get scale by dividing the desired radius by the distance of p3 to the projected point (i.e. the "current" radius)
28.     float scale = radius / distance(p3, D);
29.
30.     // Scale line *AD* (A to the projected point) by that value; this gives point C
31.     vec2 C = A + scale * (D - A);
32.
33.     // Point B (output) is C offset by the normal of the line (normalized vector of D to p3) multiplied by the desired radius
34.     vec2 B = C + radius * normalize(p3 - D);
35.
36.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
37.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
38.
39.     // Pixel color
40.     vec3 col = vec3(1.);
41.
42.     // Draw lines
43.     if (sdSegment(uv, p1, p2) < 0.01 * uvscale) col = vec3(0.);
44.     if (sdSegment(uv, p3, p3 + 1.3 * (A - p3)) < 0.01 * uvscale) col = vec3(1., 0., 0.);
45.     if (sdSegment(uv, C, B) < 0.01 * uvscale) col = vec3(0., 1., 0.);
46.
47.     // Draw circle (proof)
48.     if (abs(distance(uv, B) - radius) < 0.01 * uvscale) col = vec3(0., 0., 1.);
49.
50.     // Draw points
51.     //if (distance(uv, p1) < 0.03 * uvscale) col = vec3(0., 0., 0.);
52.     //if (distance(uv, p2) < 0.03 * uvscale) col = vec3(0., 0., 0.);
53.     //if (distance(uv, p3) < 0.03 * uvscale) col = vec3(1., 0., 0.);
54.     if (distance(uv, A) < 0.03 * uvscale) col = vec3(1., 0., 0.);
55.     //if (distance(uv, D) < 0.03 * uvscale) col = vec3(1., 0., 0.);
56.     if (distance(uv, C) < 0.03 * uvscale) col = vec3(0., 1., 0.);
57.     if (distance(uv, B) < 0.03 * uvscale) col = vec3(0., 0., 1.);
58.
59.     // Output to screen
60.     fragColor = vec4(col, 1.);
61. }
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.

Last edited: Jun 4, 2023
4. ### c0d3_m0nk3y

Joined:
Oct 21, 2021
Posts:
532
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).

Code (CSharp):
1. float sdSegment( in vec2 p, in vec2 a, in vec2 b ) // from: https://iquilezles.org/articles/distfunctions2d/
2. { 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 ); }
3.
4. void mainImage( out vec4 fragColor, in vec2 fragCoord )
5. {
6.     // Centered uv from -1 to 1
7.     vec2 uv = vec2((fragCoord.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * fragCoord.y / iResolution.y - 1.);
8.     vec2 muv = vec2((iMouse.x - 0.5*iResolution.x) / (0.5*iResolution.y), 2. * iMouse.y / iResolution.y - 1.);
9.     float uvscale = 10.; uv *= uvscale, muv *= uvscale; // scale uv
10.
11.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
12.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
13.
14.     // Points
15.     vec2 p1 = vec2(-12.37, -7.65), p2 = vec2(15.64, 4.07); // line points
16.     vec2 p3 = vec2(2.8, 8.73); // player point
17.     vec2 A = p1 + 0.3 * (p2 - p1); // A
18.
20.     float radius = iMouse.x < 1.5 ? 5. : 10. * iMouse.x / iResolution.x; // arbitrary value chosen by user
21.
22.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23.     // BEGIN c0d3_m0nk3y
24.
25.     // First let's calculate the normalized vector from P1 to P2:
26.     vec2 P = normalize(p2 - p1);
27.
28.     // Next let's calculate the normalized vector from A to P3:
29.     vec2 D = normalize(p3 - A);
30.
31.     // Calculate length of hypotenuse (distance from A to B)
32.     float tmp = dot(P, D);
33.     float c = radius / sqrt(1.0 - tmp * tmp);
34.
35.     // Given the length c, it's now easy to calculate the center B:
36.     vec2 B = A + c * D;
37.
38.     // END c0d3_m0nk3y
39.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
40.
41.     // Calculate C only for visualization
42.     vec2 C = B + radius * vec2(P.y, -P.x);
43.
44.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
45.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
46.
47.     // Pixel color
48.     vec3 col = vec3(1.);
49.
50.     // Draw lines
51.     if (sdSegment(uv, p1, p2) < 0.01 * uvscale) col = vec3(0.);
52.     if (sdSegment(uv, p3, p3 + 1.3 * (A - p3)) < 0.01 * uvscale) col = vec3(1., 0., 0.);
53.     if (sdSegment(uv, C, B) < 0.01 * uvscale) col = vec3(0., 1., 0.);
54.
55.     // Draw circle (proof)
56.     if (abs(distance(uv, B) - radius) < 0.01 * uvscale) col = vec3(0., 0., 1.);
57.
58.     // Draw points
59.     if (distance(uv, A) < 0.03 * uvscale) col = vec3(1., 0., 0.);
60.     if (distance(uv, C) < 0.03 * uvscale) col = vec3(0., 1., 0.);
61.     if (distance(uv, B) < 0.03 * uvscale) col = vec3(0., 0., 1.);
62.
63.     // Output to screen
64.     fragColor = vec4(col, 1.);
65. }
66.

5. ### Quatum1000

Joined:
Oct 5, 2014
Posts:
888
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. 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.

Code (CSharp):
1.
2. using UnityEngine;
3.
4. public struct PointLineD
5. {
6.     public double x1;
7.     public double y1;
8.     public double x2;
9.     public double y2;
10. }
11.
12. public struct PointD
13. {
14.     public double x;
15.     public double y;
16. }
17.
18. public struct PlayerStruct
19. {
20.     public double x;
21.     public double y;
22.     public double dirx;
23.     public double diry;
24.     public double colliderSize;
25.     public int NearestLine;
26. }
27.
28. public Vector2 CircleLineCollision(ref PlayerStruct player, ref PointD p1, ref PointD p2)
29. {
30.     double ix, iy, nx, ny, dx, dy, d, a, b, t;
31.     Vector2 result = Vector2.zero;
32.     PointLineD pl = new PointLineD();
33.
34.     // Calculate the offset line
35.     double segLen, ox, oy;
36.     dx = p2.x - p1.x;
37.     dy = p2.y - p1.y;
38.     segLen = Mathf.Sqrt((float)(dx * dx + dy * dy));
39.     ox = dy * player.colliderSize / segLen;
40.     oy = -dx * player.colliderSize / segLen;
41.     pl.x1 = p1.x + ox;
42.     pl.y1 = p1.y + oy;
43.     pl.x2 = p2.x + ox;
44.     pl.y2 = p2.y + oy;
45.
46.     // Calculate the normals of the offset line
47.     nx = pl.y2 - pl.y1;
48.     ny = pl.x1 - pl.x2;
49.
50.     // Calculate the difference between the player's position and the first point of the offset line
51.     dx = player.x - pl.x1;
52.     dy = player.y - pl.y1;
53.
54.     // Project the player vector onto the normals of the offset line
55.     d = nx * dx + ny * dy;
56.
57.     // Case: Player is coming from below
58.     if (d < 0.0)
59.     {
60.         result.x = (float)(player.x + player.dirx);
61.         result.y = (float)(player.y + player.diry);
62.         return result;
63.     }
64.
65.     a = (pl.y2 - pl.y1) * player.dirx - (pl.x2 - pl.x1) * player.diry;
66.     if (a != 0.0)
67.     {
68.         b = (pl.x1 - player.x) * (pl.y2 - pl.y1) - (pl.y1 - player.y) * (pl.x2 - pl.x1);
69.         t = b / a;
70.         if (t >= 0.0 && t <= 1.0)
71.         {
72.             ix = player.x + t * player.dirx;
73.             iy = player.y + t * player.diry;
74.
75.             dx = ix - pl.x1;
76.             dy = iy - pl.y1;
77.
78.             // !!! **************************************************************************************
79.             // !!! here is the error!!!! why the heck is the left collider point position wrong only..
80.             // !!! **************************************************************************************
81.             if (Mathf.Sqrt((float)(dx * dx + dy * dy)) <= segLen)
82.             {
83.                 result.x = (float)ix;
84.                 result.y = (float)iy;
85.                 return result;
86.             }
87.         }
88.     }
89.
90.     // Case: Player passes by the offset line
91.     result.x = (float)(player.x + player.dirx);
92.     result.y = (float)(player.y + player.diry);
93.     return result;
94. }
95.
96.
I know it's difficultly to find out what math cause the problem here.. but perhaps you have an idea..
thanks a lot

6. ### UhOhItsMoving

Joined:
May 25, 2022
Posts:
67
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.