In a 2D game, i'm trying to repositionate a circle after it passed through a segment. Taking the picture as example situation, i need to reposition back the circle so that B will become the tangent point on the circle.
The informations that i have are:
What i'm doing is to calculate the coordinates of point C, then coordinates of point B, and then subtract vector AB from AC (radius), and repositionate x,y back of -BC.
There is something wrong in the algorithm because after that the orthogonal distance between the center and the line is never exactly like the radius.
Len = Sqrt( dirx^2 + diry^2 )
dx = dirx / Len
dy = diry / Len
// these should be coordinates of point C
tangX = x + radius * dx
tangY = y + radius * dy
// these should be coordinates of point B
wrongX = x + distance* dx
wrongY = y + distance* dy
//these should be vector BC
BCx = tangX - wrongX;
BCy = tangY - wrongY;
// now i reposition the center back of BC
x = x - BCx
y = y - BCy
The center is repositionated in the correct direction but never enough to match the distance AC.
In other words:
I want to move the center and make B to be the tangent point between the circle and the line. Let me explain you in other words the problem: as you can see length of AC is equal to my radius. The radius is 40. in my log, i print out the value of the distance between A and B. Taking the image as example such distance would be something like 30, after my algorithm i expect that AB is 40 (the radius, AC) but it's always an other number, never 40
This issue is relatively easy to solve, if formulated appropriately. It seems a simple case of collision detection, where you're seeking to adjust the object's position after detecting the collision. The intuitive way to frame it is: you have an event telling you your object crossed a boundary. Its last motion vector is (dirX, dirY)
and you're given the delta
your object crossed beyond the boundary. All you need to do is to move your object backwards, the same delta it crossed beyond the boundary, along the same motion vector.
And instead of reasoning about the problem in terms of tangent points, you can simply reason about it in terms of the center coordinates of the object itself.
Now, the following parameters are given input:
- let
x, y
be the last coordinates of your object's center and r
its radius
- let
(dirX, dirY)
be your motion vector
- let
alpha
be the angle of your motion vector to the x axis (you can compute it from the motion vector itself)
- let
delta
be the distance crossed beyond the boundary
The problem's resolution reduces to the following:
And here's an executable code sample in python 2 that implements and tests that solution:
from math import sin, cos, atan, fabs as abs, sqrt, pow
from collections import namedtuple
# utility types and functions
Disk = namedtuple('Disk', 'x y radius')
MotionVector = namedtuple('MotionVector', 'x y')
def sqr(d):
return pow(d, 2)
# the actual implementation of the solution
def reposition_disk(disk, delta, motion_vector):
# we start by computing the angle to the x axis of the motion vector
alpha = atan(motion_vector.y / motion_vector.x)
# then we compute the displacement we should apply
# to our object along both axes
dX = cos(alpha) * delta
dY = sin(alpha) * delta
# and we update the object's coordinates
return Disk(disk.x - dX, disk.y - dY, disk.radius)
# a test method to exercise our implementation
def test_reposition_disk():
# initialiasing our disk at a position where it is supposed
# to have crossed a boundary
disk = Disk(80, 70, 40)
# the disk was moving along the following motion vector:
motion_vector = MotionVector(10, -5)
delta = 5
updated = reposition_disk(disk, delta, motion_vector)
# now we need to compute the coordinates of the
# point of the boundary the disk should have stopped at
alpha = atan(motion_vector.y / motion_vector.x)
bX = disk.x + (disk.radius - delta) * cos(alpha)
bY = disk.y + (disk.radius - delta) * sin(alpha)
# and finally we have to assert that the distance from
# the object's new coordinates to the boundary is equal
# to its radius
distance = sqrt(sqr(updated.x - bX ) + sqr(updated.y - bY))
print('Distance to boundary : %f' % distance)
assert abs(distance - disk.radius) < 0.01
test_reposition_disk()
Addendum
I went through the code you pasted and unless I am mistaken, there is a problem with the logic through which, you are checking for collision and computing the distance from the center of the circle to the intersecting line. An accurate implementation of that logic implies solving a quadratic equation (check this link for future reference) to find out whether the line intersects the circle. Then to compute the distance you'll have to solve an equation based on the dot product of the direction vector of the intersecting line, and the orthogonal line passing through the center of the circle.
Here's a python snippet illustrating the first part (figuring out whether or not the line intersects the circle) and loosely following the steps detailed in the above link:
Line = namedtuple('Line', 'x1 y1 x2 y2')
# currying the square out of pow
def sqr(d):
return pow(d, 2)
# function to compute the line equation
# out of the coordinates of a couple of
# points
def coords_to_affine(line):
# y = hx + j
h = (line.y2 - line.y1) / (line.x2 - line.x1)
j = y1 - (h * x1)
return (h, j)
# function to compute the quadratic equation
# parameters
def compute_quadratic_params(disk, line):
(h, j) = coords_to_affine(line)
(p, q, r) = (disk.x, disk.y, disk.r)
a = sqr(h) + 1
b = 2 * ((h * j) - (h * q) - p)
c = sqr(q) - sqr(r) + sqr(p) - (2 * (j * q)) + sqr(j)
return (a, b, c)
# function to test whether a given circle
# intersects a line
def disk_intersects_line(a, b, c):
# true if (b² - 4ac) >= 0
return (sqr(b) - (4 * a * c)) >= 0