Ответ 1
После долгих лет рассмотрения этой проблемы и никогда не придумал идеального решения, я, наконец, сделал это!
Это довольно простой алгоритм прямой обработки, не требуется цикл и аппроксимации.
Вот как это работает на более высоком уровне:
- Рассчитайте время пересечения с каждой боковой плоскостью, если путь от текущей точки до будущей точки пересекает эту плоскость.
- Проверьте каждый боковой квадрант для одностороннего пересечения, верните пересечение.
- Определите угол, с которым сталкивается круг.
- Решите треугольник между текущей точкой, углом и пересекающимся центром (радиус от угла).
- Рассчитать время, нормальный и центр пересечения.
И теперь к деталям gory!
Ввод функции ограничен (имеет левый, верхний, правый, нижний) и текущую точку (начало) и будущую точку (конец).
Вывод - это класс, называемый пересечением, который имеет x, y, time, nx и ny.
- {x, y} - центр круга во время пересечения.
- время - это значение от 0 до 1, где 0 находится в начале, а 1 - в конце
- {nx, ny} - нормаль, используемая для отражения скорости для определения новой скорости круга
Мы начинаем с часто используемых кеширующих переменных:
float L = bounds.left;
float T = bounds.top;
float R = bounds.right;
float B = bounds.bottom;
float dx = end.x - start.x;
float dy = end.y - start.y;
И вычисление времени пересечения с каждой боковой плоскостью (если вектор между началом и концом проходит по этой плоскости):
float ltime = Float.MAX_VALUE;
float rtime = Float.MAX_VALUE;
float ttime = Float.MAX_VALUE;
float btime = Float.MAX_VALUE;
if (start.x - radius < L && end.x + radius > L) {
ltime = ((L - radius) - start.x) / dx;
}
if (start.x + radius > R && end.x - radius < R) {
rtime = (start.x - (R + radius)) / -dx;
}
if (start.y - radius < T && end.y + radius > T) {
ttime = ((T - radius) - start.y) / dy;
}
if (start.y + radius > B && end.y - radius < B) {
btime = (start.y - (B + radius)) / -dy;
}
Теперь мы пытаемся увидеть, строго ли это боковое пересечение (а не угол). Если точка столкновения лежит сбоку, верните пересечение:
if (ltime >= 0.0f && ltime <= 1.0f) {
float ly = dy * ltime + start.y;
if (ly >= T && ly <= B) {
return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0 );
}
}
else if (rtime >= 0.0f && rtime <= 1.0f) {
float ry = dy * rtime + start.y;
if (ry >= T && ry <= B) {
return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0 );
}
}
if (ttime >= 0.0f && ttime <= 1.0f) {
float tx = dx * ttime + start.x;
if (tx >= L && tx <= R) {
return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1 );
}
}
else if (btime >= 0.0f && btime <= 1.0f) {
float bx = dx * btime + start.x;
if (bx >= L && bx <= R) {
return new Intersection( bx, dy * btime + start.y, btime, 0, 1 );
}
}
Мы дошли так далеко, чтобы мы знали, что там нет пересечения, или он столкнулся с углом. Нам нужно определить угол:
float cornerX = Float.MAX_VALUE;
float cornerY = Float.MAX_VALUE;
if (ltime != Float.MAX_VALUE) {
cornerX = L;
} else if (rtime != Float.MAX_VALUE) {
cornerX = R;
}
if (ttime != Float.MAX_VALUE) {
cornerY = T;
} else if (btime != Float.MAX_VALUE) {
cornerY = B;
}
// Account for the times where we don't pass over a side but we do hit it corner
if (cornerX != Float.MAX_VALUE && cornerY == Float.MAX_VALUE) {
cornerY = (dy > 0.0f ? B : T);
}
if (cornerY != Float.MAX_VALUE && cornerX == Float.MAX_VALUE) {
cornerX = (dx > 0.0f ? R : L);
}
Теперь у нас достаточно информации для решения для треугольника. Это использует формулу расстояния, находя угол между двумя векторами и закон синусов (дважды):
double inverseRadius = 1.0 / radius;
double lineLength = Math.sqrt( dx * dx + dy * dy );
double cornerdx = cornerX - start.x;
double cornerdy = cornerY - start.y;
double cornerdist = Math.sqrt( cornerdx * cornerdx + cornerdy * cornerdy );
double innerAngle = Math.acos( (cornerdx * dx + cornerdy * dy) / (lineLength * cornerdist) );
double innerAngleSin = Math.sin( innerAngle );
double angle1Sin = innerAngleSin * cornerdist * inverseRadius;
// The angle is too large, there cannot be an intersection
if (Math.abs( angle1Sin ) > 1.0f) {
return null;
}
double angle1 = Math.PI - Math.asin( angle1Sin );
double angle2 = Math.PI - innerAngle - angle1;
double intersectionDistance = radius * Math.sin( angle2 ) / innerAngleSin;
Теперь, когда мы решили все стороны и углы, мы можем определить время и все остальное:
// Solve for time
float time = (float)(intersectionDistance / lineLength);
// If time is outside the boundaries, return null. This algorithm can
// return a negative time which indicates the previous intersection.
if (time > 1.0f || time < 0.0f) {
return null;
}
// Solve the intersection and normal
float ix = time * dx + start.x;
float iy = time * dy + start.y;
float nx = (float)((ix - cornerX) * inverseRadius);
float ny = (float)((iy - cornerY) * inverseRadius);
return new Intersection( ix, iy, time, nx, ny );
Woo! Это было весело... у этого есть много возможностей для улучшения, насколько эффективность идет. Вы можете переупорядочить проверку пересечения сторон, чтобы убежать как можно раньше, делая как можно меньше расчетов.
Я надеялся, что это будет способ сделать это без тригонометрических функций, но я должен был сдаться!
Вот пример того, как я вызываю его и используя его для вычисления нового положения круга с использованием нормали для отражения и времени пересечения для вычисления величины отражения:
Intersection inter = handleIntersection( bounds, start, end, radius );
if (inter != null)
{
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.x + ndx * remainingTime;
float newy = inter.y + ndy * remainingTime;
// new circle position = {newx, newy}
}
И я разместил полный код на pastebin с полностью интерактивным примером, где вы можете нарисовать начальную и конечную точки и он показывает вам время и результат отскока прямоугольника.
Если вы хотите, чтобы он сразу запустился, вам нужно будет скачать код из мой блог, иначе вставьте его в свой собственный Java2D приложение.
EDIT: Я изменил код в pastebin, чтобы также включить точку столкновения, а также сделал некоторые улучшения скорости.
EDIT: Вы можете изменить это для вращающегося прямоугольника, используя этот угол прямоугольника, чтобы не вращать прямоугольник с начальным и конечным точками круга. Вы выполните проверку пересечения, а затем поверните полученные точки и нормали.
EDIT: Я изменил код на pastebin, чтобы выйти раньше, если ограничивающий объем пути круга не пересекается с прямоугольником.