Wrong repositioning circle after collision with se

2019-08-29 06:42发布

问题:

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:

  • x,y of the circle

  • Radius of the circle

  • dirx, diry: Direction of x,y the circle is having

  • Distance between A and B

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

回答1:

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